/* * Copyright (c) 2026 The WebRTC project authors. All Rights Reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #include "modules/video_coding/sframe_packet_buffer.h" #include #include #include #include #include #include #include "modules/rtp_rtcp/source/rtp_packet_received.h" #include "modules/rtp_rtcp/source/sframe_descriptor.h" #include "modules/rtp_rtcp/source/sframe_rtp_packet_received.h" #include "test/gtest.h" namespace webrtc { namespace { template struct overloaded : Ts... { using Ts::operator()...; }; template overloaded(Ts...) -> overloaded; constexpr size_t kBufferSize = 16; class SFramePacketBufferTest : public ::testing::Test { protected: SFramePacketBufferTest() : buffer_(kBufferSize) {} enum IsStart { kNotStart, kStart }; enum IsEnd { kNotEnd, kEnd }; std::unique_ptr MakePacket( uint16_t seq_num, IsStart start, IsEnd end, uint32_t timestamp = 1000, uint8_t payload_type = 96, SframeEncryptionLevel encryption_level = SframeEncryptionLevel::kFrame) { auto rtp = std::make_unique(); rtp->SetSequenceNumber(seq_num); rtp->SetTimestamp(timestamp); rtp->SetPayloadType(payload_type); SFrameDescriptor desc; desc.start = (start == kStart); desc.end = (end == kEnd); desc.encryption_level = encryption_level; return std::make_unique(std::move(rtp), desc); } struct TestResult { SFramePacketBuffer::InsertResult status = SFramePacketBuffer::InsertResult::kNoFrame; SframeEncryptionLevel encryption_level = SframeEncryptionLevel::kFrame; std::vector> packets; }; bool BufferCleared(SFramePacketBuffer::InsertResult r) { return r == SFramePacketBuffer::InsertResult::kBufferCleared; } TestResult InsertInto(SFramePacketBuffer& buf, std::unique_ptr packet) { auto insert_result = buf.InsertPacket(std::move(packet)); return std::visit( overloaded{ [](SFramePacketBuffer::AssembledFrame& frame) -> TestResult { return {.encryption_level = frame.encryption_level, .packets = std::move(frame.packets)}; }, [](SFramePacketBuffer::InsertResult& status) -> TestResult { return {.status = status}; }, }, insert_result); } TestResult Insert( uint16_t seq_num, IsStart start, IsEnd end, uint32_t timestamp = 1000, uint8_t payload_type = 96, SframeEncryptionLevel encryption_level = SframeEncryptionLevel::kFrame) { return InsertInto(buffer_, MakePacket(seq_num, start, end, timestamp, payload_type, encryption_level)); } SFramePacketBuffer buffer_; }; TEST_F(SFramePacketBufferTest, SinglePacketFrame) { auto result = Insert(100, kStart, kEnd); ASSERT_EQ(result.packets.size(), 1u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 100); } TEST_F(SFramePacketBufferTest, TwoPacketFrame) { EXPECT_TRUE(Insert(100, kStart, kNotEnd).packets.empty()); auto result = Insert(101, kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 2u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 100); EXPECT_EQ(result.packets[1]->SequenceNumber(), 101); } TEST_F(SFramePacketBufferTest, ThreePacketFrame) { EXPECT_TRUE(Insert(200, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(201, kNotStart, kNotEnd).packets.empty()); auto result = Insert(202, kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 3u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 200); EXPECT_EQ(result.packets[1]->SequenceNumber(), 201); EXPECT_EQ(result.packets[2]->SequenceNumber(), 202); } TEST_F(SFramePacketBufferTest, TwoPacketFrameReversed) { EXPECT_TRUE(Insert(101, kNotStart, kEnd).packets.empty()); auto result = Insert(100, kStart, kNotEnd); ASSERT_EQ(result.packets.size(), 2u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 100); EXPECT_EQ(result.packets[1]->SequenceNumber(), 101); } TEST_F(SFramePacketBufferTest, ThreePacketFrameMiddleLast) { EXPECT_TRUE(Insert(300, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(302, kNotStart, kEnd).packets.empty()); auto result = Insert(301, kNotStart, kNotEnd); ASSERT_EQ(result.packets.size(), 3u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 300); EXPECT_EQ(result.packets[1]->SequenceNumber(), 301); EXPECT_EQ(result.packets[2]->SequenceNumber(), 302); } TEST_F(SFramePacketBufferTest, TwoConsecutiveSinglePacketFrames) { auto r1 = Insert(50, kStart, kEnd, /*timestamp=*/1000); ASSERT_EQ(r1.packets.size(), 1u); auto r2 = Insert(51, kStart, kEnd, /*timestamp=*/2000); ASSERT_EQ(r2.packets.size(), 1u); EXPECT_EQ(r2.packets[0]->SequenceNumber(), 51); } TEST_F(SFramePacketBufferTest, TwoFramesBackToBack) { // Frame 1: seq 10-11 EXPECT_TRUE(Insert(10, kStart, kNotEnd, /*timestamp=*/100).packets.empty()); auto r1 = Insert(11, kNotStart, kEnd, /*timestamp=*/100); ASSERT_EQ(r1.packets.size(), 2u); // Frame 2: seq 12-13 EXPECT_TRUE(Insert(12, kStart, kNotEnd, /*timestamp=*/200).packets.empty()); auto r2 = Insert(13, kNotStart, kEnd, /*timestamp=*/200); ASSERT_EQ(r2.packets.size(), 2u); } TEST_F(SFramePacketBufferTest, IncompleteFrameMissingEnd) { EXPECT_TRUE(Insert(100, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(101, kNotStart, kNotEnd).packets.empty()); // Neither packet completes a frame, but completing the frame later works. auto result = Insert(102, kNotStart, kEnd); EXPECT_EQ(result.packets.size(), 3u); } TEST_F(SFramePacketBufferTest, IncompleteFrameMissingStart) { EXPECT_TRUE(Insert(101, kNotStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(102, kNotStart, kEnd).packets.empty()); // Packets are buffered but no S-bit found, so adding it completes the frame. auto result = Insert(100, kStart, kNotEnd); EXPECT_EQ(result.packets.size(), 3u); } TEST_F(SFramePacketBufferTest, IncompleteFrameGap) { // S-bit at 100, gap at 101, E-bit at 102. EXPECT_TRUE(Insert(100, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(102, kNotStart, kEnd).packets.empty()); // Filling the gap completes the frame. auto result = Insert(101, kNotStart, kNotEnd); EXPECT_EQ(result.packets.size(), 3u); } TEST_F(SFramePacketBufferTest, DuplicatePacketDropped) { EXPECT_TRUE(Insert(100, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(100, kStart, kNotEnd).packets.empty()); // Original packet still in buffer -- completing the frame proves it. auto result = Insert(101, kNotStart, kEnd); EXPECT_EQ(result.packets.size(), 2u); } TEST_F(SFramePacketBufferTest, TBitMismatchDropsFrame) { EXPECT_TRUE( Insert(100, kStart, kNotEnd, 1000, 96, SframeEncryptionLevel::kFrame) .packets.empty()); auto result = Insert(101, kNotStart, kEnd, 1000, 96, SframeEncryptionLevel::kPacket); EXPECT_TRUE(result.packets.empty()); // Dropped packets don't block subsequent frames. auto r2 = Insert(102, kStart, kEnd, /*timestamp=*/2000); EXPECT_FALSE(r2.packets.empty()); } TEST_F(SFramePacketBufferTest, PayloadTypeMismatchDropsFrame) { EXPECT_TRUE( Insert(100, kStart, kNotEnd, 1000, /*payload_type=*/96).packets.empty()); auto result = Insert(101, kNotStart, kEnd, 1000, /*payload_type=*/97); EXPECT_TRUE(result.packets.empty()); auto r2 = Insert(102, kStart, kEnd, /*timestamp=*/2000); EXPECT_FALSE(r2.packets.empty()); } TEST_F(SFramePacketBufferTest, TimestampMismatchDropsFrame) { EXPECT_TRUE(Insert(100, kStart, kNotEnd, /*timestamp=*/1000).packets.empty()); auto result = Insert(101, kNotStart, kEnd, /*timestamp=*/2000); EXPECT_TRUE(result.packets.empty()); auto r2 = Insert(102, kStart, kEnd, /*timestamp=*/3000); EXPECT_FALSE(r2.packets.empty()); } TEST_F(SFramePacketBufferTest, PacketizedBitPropagated) { auto result = Insert(100, kStart, kEnd, 1000, 96, SframeEncryptionLevel::kPacket); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.encryption_level, SframeEncryptionLevel::kPacket); } TEST_F(SFramePacketBufferTest, NonPacketizedBitPropagated) { auto result = Insert(100, kStart, kEnd, 1000, 96, SframeEncryptionLevel::kFrame); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.encryption_level, SframeEncryptionLevel::kFrame); } TEST_F(SFramePacketBufferTest, AssembledPacketsRemovedFromBuffer) { auto r1 = Insert(100, kStart, kEnd); ASSERT_FALSE(r1.packets.empty()); // Slot is freed after assembly. Re-inserting assembles a new frame. auto r2 = Insert(100, kStart, kEnd); ASSERT_FALSE(r2.packets.empty()); EXPECT_EQ(r2.packets.size(), 1u); } TEST_F(SFramePacketBufferTest, MultiPacketFrameRemovedFromBuffer) { Insert(100, kStart, kNotEnd); Insert(101, kNotStart, kNotEnd); auto result = Insert(102, kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 3u); // Slots freed -- a new frame at same seq range can be inserted. auto r2 = Insert(100, kStart, kEnd, /*timestamp=*/9000); EXPECT_FALSE(r2.packets.empty()); } TEST_F(SFramePacketBufferTest, ClearRemovesAll) { Insert(100, kStart, kNotEnd); Insert(101, kNotStart, kNotEnd); buffer_.Clear(); // After clear, inserting a new single-packet frame works (no stale data). auto result = Insert(200, kStart, kEnd, /*timestamp=*/5000); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->SequenceNumber(), 200); } TEST_F(SFramePacketBufferTest, ClearToRemovesOldPackets) { Insert(100, kStart, kNotEnd, /*timestamp=*/1000); Insert(101, kNotStart, kNotEnd, /*timestamp=*/1000); Insert(102, kNotStart, kNotEnd, /*timestamp=*/1000); buffer_.ClearTo(101); // Packets 100-101 cleared, packet 102 remains. // Completing a frame starting from 102 should work. auto result = Insert(103, kNotStart, kEnd, /*timestamp=*/1000); // 102 has no S-bit and 100-101 are gone, so this can't assemble. EXPECT_TRUE(result.packets.empty()); } TEST_F(SFramePacketBufferTest, ClearToIsInclusive) { Insert(100, kStart, kEnd, /*timestamp=*/1000); // Frame was assembled, buffer is empty. Insert new incomplete frame. Insert(101, kStart, kNotEnd, /*timestamp=*/2000); Insert(102, kNotStart, kNotEnd, /*timestamp=*/2000); buffer_.ClearTo(102); // Both 101 and 102 should be cleared. A new frame after works. auto result = Insert(103, kStart, kEnd, /*timestamp=*/3000); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->SequenceNumber(), 103); } TEST_F(SFramePacketBufferTest, ClearToBeforeFirstPacketIsNoop) { Insert(100, kStart, kNotEnd); // ClearTo a seq_num before the first received packet should be a no-op. buffer_.ClearTo(50); // Packet 100 is still there -- completing the frame proves it. auto result = Insert(101, kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 2u); } TEST_F(SFramePacketBufferTest, SeqNumWraparound) { uint16_t seq = 0xFFFE; EXPECT_TRUE(Insert(seq, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(seq + 1, kNotStart, kNotEnd).packets.empty()); // seq + 2 wraps to 0. auto result = Insert(static_cast(seq + 2), kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 3u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 0xFFFE); EXPECT_EQ(result.packets[1]->SequenceNumber(), 0xFFFF); EXPECT_EQ(result.packets[2]->SequenceNumber(), 0x0000); } TEST_F(SFramePacketBufferTest, SinglePacketAtWraparound) { auto result = Insert(0xFFFF, kStart, kEnd); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->SequenceNumber(), 0xFFFF); } TEST_F(SFramePacketBufferTest, ReorderedPacketUnwrapsToNegative) { // First packet at seq 2 unwraps to 2. Then a reordered packet at raw seq // 65535 unwraps to -1 (backward by 3 is shorter than forward by 65533). // ToIdx must handle the negative unwrapped value correctly. EXPECT_TRUE(Insert(2, kNotStart, kEnd).packets.empty()); auto result = Insert(0xFFFF, kStart, kNotEnd); EXPECT_TRUE(result.packets.empty()); // Both packets are buffered — insert the middle to complete the frame. auto r2 = Insert(0, kNotStart, kNotEnd); EXPECT_TRUE(r2.packets.empty()); auto r3 = Insert(1, kNotStart, kNotEnd); ASSERT_EQ(r3.packets.size(), 4u); EXPECT_EQ(r3.packets[0]->SequenceNumber(), 0xFFFF); EXPECT_EQ(r3.packets[3]->SequenceNumber(), 2); } TEST_F(SFramePacketBufferTest, CollisionClearsBuffer) { // seq 0 and seq 16 both map to slot 0 in a size-16 buffer. EXPECT_TRUE(Insert(0, kStart, kNotEnd, /*timestamp=*/100).packets.empty()); // Insert 16 which collides with slot 0. Should clear the buffer. auto result = Insert(16, kStart, kNotEnd, /*timestamp=*/200); EXPECT_TRUE(BufferCleared(result.status)); } TEST_F(SFramePacketBufferTest, BufferClearedOnCollision) { // Create a small buffer so collisions happen quickly. SFramePacketBuffer small_buffer(4); // Fill all 4 slots with non-colliding packets. small_buffer.InsertPacket(MakePacket(0, kStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(1, kNotStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(2, kNotStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(3, kNotStart, kNotEnd, 100)); // Insert packet that collides (seq 4 maps to slot 0 in size-4 buffer). // Should clear and re-insert. auto result = small_buffer.InsertPacket(MakePacket(4, kStart, kEnd, 200)); // Buffer cleared — caller should request a keyframe. ASSERT_TRUE(std::holds_alternative(result)); EXPECT_EQ(std::get(result), SFramePacketBuffer::InsertResult::kBufferCleared); } TEST_F(SFramePacketBufferTest, VeryOldPacketDropped) { // Insert a packet to establish the window. Insert(1000, kStart, kEnd); // Try to insert a packet far before the window. With buffer_size=16, // any packet >= 16 positions back is dropped. auto result = Insert(900, kStart, kEnd); // Packet is dropped (too old), no assembly. EXPECT_TRUE(result.packets.empty()); } TEST_F(SFramePacketBufferTest, TimestampPreservedInResult) { auto result = Insert(100, kStart, kEnd, /*timestamp=*/42000); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->Timestamp(), 42000u); } TEST_F(SFramePacketBufferTest, PayloadTypePreservedInResult) { auto result = Insert(100, kStart, kEnd, 1000, /*payload_type=*/111); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->PayloadType(), 111); } TEST_F(SFramePacketBufferTest, InterleavedFramesAssembleIndependently) { // Frame A: seq 10-11 (ts 1000), Frame B: seq 12-13 (ts 2000). // Insert them interleaved: A-start, B-start, A-end, B-end. EXPECT_TRUE(Insert(10, kStart, kNotEnd, /*timestamp=*/1000).packets.empty()); EXPECT_TRUE(Insert(12, kStart, kNotEnd, /*timestamp=*/2000).packets.empty()); // Completing frame A. auto r1 = Insert(11, kNotStart, kEnd, /*timestamp=*/1000); ASSERT_EQ(r1.packets.size(), 2u); EXPECT_EQ(r1.packets[0]->SequenceNumber(), 10); // Completing frame B. auto r2 = Insert(13, kNotStart, kEnd, /*timestamp=*/2000); ASSERT_EQ(r2.packets.size(), 2u); EXPECT_EQ(r2.packets[0]->SequenceNumber(), 12); } TEST_F(SFramePacketBufferTest, FrameAfterDroppedFrameStillAssembles) { // Frame 1 with timestamp mismatch -> dropped. Insert(100, kStart, kNotEnd, /*timestamp=*/1000); Insert(101, kNotStart, kEnd, /*timestamp=*/9999); // mismatch // Frame 2 is valid. auto result = Insert(102, kStart, kEnd, /*timestamp=*/2000); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->SequenceNumber(), 102); } TEST_F(SFramePacketBufferTest, ClearToPreventsLatePacketFromMovingWindow) { // Insert and assemble frame at seq 100. auto r1 = Insert(100, kStart, kEnd, /*timestamp=*/1000); ASSERT_FALSE(r1.packets.empty()); // Advance the window past seq 105. Insert(101, kStart, kNotEnd, /*timestamp=*/2000); Insert(102, kNotStart, kNotEnd, /*timestamp=*/2000); Insert(103, kNotStart, kNotEnd, /*timestamp=*/2000); Insert(104, kNotStart, kNotEnd, /*timestamp=*/2000); Insert(105, kNotStart, kNotEnd, /*timestamp=*/2000); buffer_.ClearTo(105); // A late packet from before the cleared point should be dropped. auto late = Insert(103, kStart, kEnd, /*timestamp=*/2000); EXPECT_TRUE(late.packets.empty()); } TEST_F(SFramePacketBufferTest, ClearToAllowsNewPacketsAfterClearedPoint) { Insert(100, kStart, kEnd, /*timestamp=*/1000); buffer_.ClearTo(100); // Packet after the cleared point should work fine. auto result = Insert(110, kStart, kEnd, /*timestamp=*/2000); ASSERT_FALSE(result.packets.empty()); EXPECT_EQ(result.packets[0]->SequenceNumber(), 110); } TEST_F(SFramePacketBufferTest, ClearToDoesNotWipeNewerPacket) { // Insert two frames in non-adjacent slots. auto r1 = Insert(0, kStart, kEnd, /*timestamp=*/1000); ASSERT_FALSE(r1.packets.empty()); // Packet in a higher slot (2 * kBufferSize away to avoid aliasing). Insert(2 * kBufferSize, kStart, kNotEnd, /*timestamp=*/3000); // ClearTo the first frame — must not affect the second. buffer_.ClearTo(0); // Completing the second frame still works. auto r2 = Insert(2 * kBufferSize + 1, kNotStart, kEnd, /*timestamp=*/3000); ASSERT_EQ(r2.packets.size(), 2u); EXPECT_EQ(r2.packets[0]->SequenceNumber(), 2 * kBufferSize); } TEST_F(SFramePacketBufferTest, FreedSlotsReuseFullBuffer) { // Use a small buffer where we can fill it entirely. SFramePacketBuffer small_buffer(4); // Frame 1: 3 packets, assembles and frees slots. small_buffer.InsertPacket(MakePacket(10, kStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(11, kNotStart, kNotEnd, 100)); auto r1 = InsertInto(small_buffer, MakePacket(12, kNotStart, kEnd, 100)); ASSERT_EQ(r1.packets.size(), 3u); // Frame 2: fills all 4 slots using the freed space. small_buffer.InsertPacket(MakePacket(13, kStart, kNotEnd, 200)); small_buffer.InsertPacket(MakePacket(14, kNotStart, kNotEnd, 200)); small_buffer.InsertPacket(MakePacket(15, kNotStart, kNotEnd, 200)); auto r2 = InsertInto(small_buffer, MakePacket(16, kNotStart, kEnd, 200)); ASSERT_EQ(r2.packets.size(), 4u); EXPECT_FALSE(BufferCleared(r2.status)); } TEST_F(SFramePacketBufferTest, FramesAfterClearToWithReordering) { // Assemble and clear past a frame. Insert(100, kStart, kEnd, /*timestamp=*/1000); Insert(101, kStart, kEnd, /*timestamp=*/2000); buffer_.ClearTo(101); // Insert a later frame first (out of order). auto r1 = Insert(110, kStart, kEnd, /*timestamp=*/4000); ASSERT_FALSE(r1.packets.empty()); EXPECT_EQ(r1.packets[0]->SequenceNumber(), 110); // Insert a frame between the cleared point and the later frame. auto r2 = Insert(105, kStart, kEnd, /*timestamp=*/3000); ASSERT_FALSE(r2.packets.empty()); EXPECT_EQ(r2.packets[0]->SequenceNumber(), 105); } TEST_F(SFramePacketBufferTest, TwoStartsWithoutEnd) { // S(101) followed by S(102) — two starts, no complete frame. EXPECT_TRUE(Insert(101, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(102, kStart, kNotEnd, /*timestamp=*/2000).packets.empty()); // Adding end for second "frame" (102-103) should assemble. auto result = Insert(103, kNotStart, kEnd, /*timestamp=*/2000); ASSERT_EQ(result.packets.size(), 2u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 102); EXPECT_EQ(result.packets[1]->SequenceNumber(), 103); } TEST_F(SFramePacketBufferTest, TwoEndsWithoutStart) { // Two E-bit packets with no preceding S-bit — no frame assembles. EXPECT_TRUE(Insert(100, kNotStart, kEnd).packets.empty()); EXPECT_TRUE(Insert(101, kNotStart, kEnd, /*timestamp=*/2000).packets.empty()); } TEST_F(SFramePacketBufferTest, EFollowedByE) { // S at 100, E at 101, then another E at 102 with different timestamp. auto r1 = Insert(100, kStart, kNotEnd, /*timestamp=*/1000); EXPECT_TRUE(r1.packets.empty()); auto r2 = Insert(101, kNotStart, kEnd, /*timestamp=*/1000); ASSERT_EQ(r2.packets.size(), 2u); // Orphan E at 102 — no start found. auto r3 = Insert(102, kNotStart, kEnd, /*timestamp=*/2000); EXPECT_TRUE(r3.packets.empty()); } TEST_F(SFramePacketBufferTest, TBitMismatchInMiddlePacketDropsFrame) { // 3-packet frame where the middle packet has a mismatched T-bit. EXPECT_TRUE( Insert(100, kStart, kNotEnd, 1000, 96, SframeEncryptionLevel::kFrame) .packets.empty()); EXPECT_TRUE( Insert(101, kNotStart, kNotEnd, 1000, 96, SframeEncryptionLevel::kPacket) .packets.empty()); auto result = Insert(102, kNotStart, kEnd, 1000, 96, SframeEncryptionLevel::kFrame); EXPECT_TRUE(result.packets.empty()); // All slots must be freed — re-inserting a fresh frame at same seqs works. auto r2 = Insert(100, kStart, kEnd, /*timestamp=*/5000); ASSERT_EQ(r2.packets.size(), 1u); } TEST_F(SFramePacketBufferTest, FrameWrapsAroundRingBuffer) { // With buffer_size=16, place S near the end and E past the wrap. // Seq 14 → slot 14, seq 15 → slot 15, seq 16 → slot 0. EXPECT_TRUE(Insert(14, kStart, kNotEnd).packets.empty()); EXPECT_TRUE(Insert(15, kNotStart, kNotEnd).packets.empty()); auto result = Insert(16, kNotStart, kEnd); ASSERT_EQ(result.packets.size(), 3u); EXPECT_EQ(result.packets[0]->SequenceNumber(), 14); EXPECT_EQ(result.packets[2]->SequenceNumber(), 16); } TEST_F(SFramePacketBufferTest, CollisionDuringMultiPacketFrameClearsBuffer) { // Start a multi-packet frame. EXPECT_TRUE(Insert(0, kStart, kNotEnd, /*timestamp=*/100).packets.empty()); EXPECT_TRUE(Insert(1, kNotStart, kNotEnd, /*timestamp=*/100).packets.empty()); // Insert a packet that collides (seq 16 → slot 0). Buffer clears. auto result = Insert(16, kStart, kNotEnd, /*timestamp=*/200); EXPECT_TRUE(BufferCleared(result.status)); } TEST_F(SFramePacketBufferTest, BufferFullBeforeEndPacket) { // Small buffer: S and middle packets fill it, E arrives too late. SFramePacketBuffer small_buffer(4); small_buffer.InsertPacket(MakePacket(0, kStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(1, kNotStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(2, kNotStart, kNotEnd, 100)); small_buffer.InsertPacket(MakePacket(3, kNotStart, kNotEnd, 100)); // Packet 4 (the end) collides with slot 0; buffer clears. auto result = InsertInto(small_buffer, MakePacket(4, kNotStart, kEnd, 100)); // Buffer cleared — the frame is lost. EXPECT_TRUE(BufferCleared(result.status)); // Only packet 4 remains; it has no S-bit so no frame assembles. EXPECT_TRUE(result.packets.empty()); } } // namespace } // namespace webrtc