| /* |
| * 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 <cstddef> |
| #include <cstdint> |
| #include <memory> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #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 <class... Ts> |
| struct overloaded : Ts... { |
| using Ts::operator()...; |
| }; |
| template <class... Ts> |
| overloaded(Ts...) -> overloaded<Ts...>; |
| |
| 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<SframeRtpPacketReceived> 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<RtpPacketReceived>(); |
| 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<SframeRtpPacketReceived>(std::move(rtp), desc); |
| } |
| struct TestResult { |
| SFramePacketBuffer::InsertResult status = |
| SFramePacketBuffer::InsertResult::kNoFrame; |
| SframeEncryptionLevel encryption_level = SframeEncryptionLevel::kFrame; |
| std::vector<std::unique_ptr<RtpPacketReceived>> packets; |
| }; |
| |
| bool BufferCleared(SFramePacketBuffer::InsertResult r) { |
| return r == SFramePacketBuffer::InsertResult::kBufferCleared; |
| } |
| |
| TestResult InsertInto(SFramePacketBuffer& buf, |
| std::unique_ptr<SframeRtpPacketReceived> 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<uint16_t>(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<SFramePacketBuffer::InsertResult>(result)); |
| EXPECT_EQ(std::get<SFramePacketBuffer::InsertResult>(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 |