| /* | |
| * Copyright (c) 2021 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 "net/dcsctp/tx/rr_send_queue.h" | |
| #include <cstddef> | |
| #include <cstdint> | |
| #include <optional> | |
| #include <vector> | |
| #include "api/units/time_delta.h" | |
| #include "api/units/timestamp.h" | |
| #include "net/dcsctp/common/internal_types.h" | |
| #include "net/dcsctp/packet/data.h" | |
| #include "net/dcsctp/public/dcsctp_handover_state.h" | |
| #include "net/dcsctp/public/dcsctp_message.h" | |
| #include "net/dcsctp/public/dcsctp_socket.h" | |
| #include "net/dcsctp/public/types.h" | |
| #include "net/dcsctp/socket/mock_dcsctp_socket_callbacks.h" | |
| #include "net/dcsctp/testing/testing_macros.h" | |
| #include "net/dcsctp/tx/send_queue.h" | |
| #include "test/gmock.h" | |
| #include "test/gtest.h" | |
| namespace dcsctp { | |
| namespace { | |
| using ::testing::SizeIs; | |
| using ::testing::UnorderedElementsAre; | |
| using ::webrtc::TimeDelta; | |
| using ::webrtc::Timestamp; | |
| constexpr Timestamp kNow = Timestamp::Zero(); | |
| constexpr StreamID kStreamID(1); | |
| constexpr PPID kPPID(53); | |
| constexpr StreamPriority kDefaultPriority(10); | |
| constexpr size_t kBufferedAmountLowThreshold = 500; | |
| constexpr size_t kOneFragmentPacketSize = 100; | |
| constexpr size_t kTwoFragmentPacketSize = 101; | |
| constexpr size_t kMtu = 1100; | |
| TEST(RRSendQueueTest, EmptyBuffer) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_TRUE(q.IsEmpty()); | |
| EXPECT_FALSE(q.Produce(kNow, kOneFragmentPacketSize).has_value()); | |
| } | |
| TEST(RRSendQueueTest, AddAndGetSingleChunk) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, {1, 2, 4, 5, 6})); | |
| EXPECT_FALSE(q.IsEmpty()); | |
| std::optional<SendQueue::DataToSend> chunk_opt = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_opt.has_value()); | |
| EXPECT_TRUE(chunk_opt->data.is_beginning); | |
| EXPECT_TRUE(chunk_opt->data.is_end); | |
| } | |
| TEST(RRSendQueueTest, CarveOutBeginningMiddleAndEnd) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(60); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_beg = | |
| q.Produce(kNow, /*max_size=*/20); | |
| ASSERT_TRUE(chunk_beg.has_value()); | |
| EXPECT_TRUE(chunk_beg->data.is_beginning); | |
| EXPECT_FALSE(chunk_beg->data.is_end); | |
| std::optional<SendQueue::DataToSend> chunk_mid = | |
| q.Produce(kNow, /*max_size=*/20); | |
| ASSERT_TRUE(chunk_mid.has_value()); | |
| EXPECT_FALSE(chunk_mid->data.is_beginning); | |
| EXPECT_FALSE(chunk_mid->data.is_end); | |
| std::optional<SendQueue::DataToSend> chunk_end = | |
| q.Produce(kNow, /*max_size=*/20); | |
| ASSERT_TRUE(chunk_end.has_value()); | |
| EXPECT_FALSE(chunk_end->data.is_beginning); | |
| EXPECT_TRUE(chunk_end->data.is_end); | |
| EXPECT_FALSE(q.Produce(kNow, kOneFragmentPacketSize).has_value()); | |
| } | |
| TEST(RRSendQueueTest, GetChunksFromTwoMessages) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(60); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), PPID(54), payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(chunk_one->data.ppid, kPPID); | |
| EXPECT_TRUE(chunk_one->data.is_beginning); | |
| EXPECT_TRUE(chunk_one->data.is_end); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.stream_id, StreamID(3)); | |
| EXPECT_EQ(chunk_two->data.ppid, PPID(54)); | |
| EXPECT_TRUE(chunk_two->data.is_beginning); | |
| EXPECT_TRUE(chunk_two->data.is_end); | |
| } | |
| TEST(RRSendQueueTest, BufferBecomesFullAndEmptied) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(600); | |
| EXPECT_LT(q.total_buffered_amount(), 1000u); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_LT(q.total_buffered_amount(), 1000u); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), PPID(54), payload)); | |
| EXPECT_GE(q.total_buffered_amount(), 1000u); | |
| // However, it's still possible to add messages. It's a soft limit, and it | |
| // might be necessary to forcefully add messages due to e.g. external | |
| // fragmentation. | |
| q.Add(kNow, DcSctpMessage(StreamID(5), PPID(55), payload)); | |
| EXPECT_GE(q.total_buffered_amount(), 1000u); | |
| std::optional<SendQueue::DataToSend> chunk_one = q.Produce(kNow, 1000); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(chunk_one->data.ppid, kPPID); | |
| EXPECT_GE(q.total_buffered_amount(), 1000u); | |
| std::optional<SendQueue::DataToSend> chunk_two = q.Produce(kNow, 1000); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.stream_id, StreamID(3)); | |
| EXPECT_EQ(chunk_two->data.ppid, PPID(54)); | |
| EXPECT_LT(q.total_buffered_amount(), 1000u); | |
| EXPECT_FALSE(q.IsEmpty()); | |
| std::optional<SendQueue::DataToSend> chunk_three = q.Produce(kNow, 1000); | |
| ASSERT_TRUE(chunk_three.has_value()); | |
| EXPECT_EQ(chunk_three->data.stream_id, StreamID(5)); | |
| EXPECT_EQ(chunk_three->data.ppid, PPID(55)); | |
| EXPECT_LT(q.total_buffered_amount(), 1000u); | |
| EXPECT_TRUE(q.IsEmpty()); | |
| } | |
| TEST(RRSendQueueTest, DefaultsToOrderedSend) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(20); | |
| // Default is ordered | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_FALSE(chunk_one->data.is_unordered); | |
| // Explicitly unordered. | |
| SendOptions opts; | |
| opts.unordered = IsUnordered(true); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload), opts); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_TRUE(chunk_two->data.is_unordered); | |
| } | |
| TEST(RRSendQueueTest, ProduceWithLifetimeExpiry) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(20); | |
| // Default is no expiry | |
| Timestamp now = kNow; | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload)); | |
| now += TimeDelta::Seconds(1000); | |
| ASSERT_TRUE(q.Produce(now, kOneFragmentPacketSize)); | |
| SendOptions expires_2_seconds; | |
| expires_2_seconds.lifetime = DurationMs(2000); | |
| // Add and consume within lifetime | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload), expires_2_seconds); | |
| now += TimeDelta::Millis(2000); | |
| ASSERT_TRUE(q.Produce(now, kOneFragmentPacketSize)); | |
| // Add and consume just outside lifetime | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload), expires_2_seconds); | |
| now += TimeDelta::Millis(2001); | |
| ASSERT_FALSE(q.Produce(now, kOneFragmentPacketSize)); | |
| // A long time after expiry | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload), expires_2_seconds); | |
| now += TimeDelta::Seconds(1000); | |
| ASSERT_FALSE(q.Produce(now, kOneFragmentPacketSize)); | |
| // Expire one message, but produce the second that is not expired. | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload), expires_2_seconds); | |
| SendOptions expires_4_seconds; | |
| expires_4_seconds.lifetime = DurationMs(4000); | |
| q.Add(now, DcSctpMessage(kStreamID, kPPID, payload), expires_4_seconds); | |
| now += TimeDelta::Millis(2001); | |
| ASSERT_TRUE(q.Produce(now, kOneFragmentPacketSize)); | |
| ASSERT_FALSE(q.Produce(now, kOneFragmentPacketSize)); | |
| } | |
| TEST(RRSendQueueTest, DiscardPartialPackets) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(120); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), PPID(54), payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_FALSE(chunk_one->data.is_end); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| q.Discard(chunk_one->data.stream_id, chunk_one->message_id); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_FALSE(chunk_two->data.is_end); | |
| EXPECT_EQ(chunk_two->data.stream_id, StreamID(2)); | |
| std::optional<SendQueue::DataToSend> chunk_three = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_three.has_value()); | |
| EXPECT_TRUE(chunk_three->data.is_end); | |
| EXPECT_EQ(chunk_three->data.stream_id, StreamID(2)); | |
| ASSERT_FALSE(q.Produce(kNow, kOneFragmentPacketSize)); | |
| // Calling it again shouldn't cause issues. | |
| q.Discard(chunk_one->data.stream_id, chunk_one->message_id); | |
| ASSERT_FALSE(q.Produce(kNow, kOneFragmentPacketSize)); | |
| } | |
| TEST(RRSendQueueTest, PrepareResetStreamsDiscardsStream) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, {1, 2, 3})); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), PPID(54), {1, 2, 3, 4, 5})); | |
| EXPECT_EQ(q.total_buffered_amount(), 8u); | |
| q.PrepareResetStream(StreamID(1)); | |
| EXPECT_EQ(q.total_buffered_amount(), 5u); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(StreamID(1))); | |
| q.CommitResetStreams(); | |
| q.PrepareResetStream(StreamID(2)); | |
| EXPECT_EQ(q.total_buffered_amount(), 0u); | |
| } | |
| TEST(RRSendQueueTest, PrepareResetStreamsNotPartialPackets) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(120); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = q.Produce(kNow, 50); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(q.total_buffered_amount(), 2 * payload.size() - 50); | |
| q.PrepareResetStream(StreamID(1)); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size() - 50); | |
| } | |
| TEST(RRSendQueueTest, EnqueuedItemsArePausedDuringStreamReset) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(50); | |
| q.PrepareResetStream(StreamID(1)); | |
| EXPECT_EQ(q.total_buffered_amount(), 0u); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size()); | |
| EXPECT_FALSE(q.Produce(kNow, kOneFragmentPacketSize).has_value()); | |
| EXPECT_TRUE(q.HasStreamsReadyToBeReset()); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(StreamID(1))); | |
| EXPECT_FALSE(q.Produce(kNow, kOneFragmentPacketSize).has_value()); | |
| q.CommitResetStreams(); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size()); | |
| std::optional<SendQueue::DataToSend> chunk_one = q.Produce(kNow, 50); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(q.total_buffered_amount(), 0u); | |
| } | |
| TEST(RRSendQueueTest, PausedStreamsStillSendPartialMessagesUntilEnd) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| constexpr size_t kPayloadSize = 100; | |
| constexpr size_t kFragmentSize = 50; | |
| std::vector<uint8_t> payload(kPayloadSize); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kFragmentSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(q.total_buffered_amount(), 2 * kPayloadSize - kFragmentSize); | |
| // This will stop the second message from being sent. | |
| q.PrepareResetStream(StreamID(1)); | |
| EXPECT_EQ(q.total_buffered_amount(), 1 * kPayloadSize - kFragmentSize); | |
| // Should still produce fragments until end of message. | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kFragmentSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.stream_id, kStreamID); | |
| EXPECT_EQ(q.total_buffered_amount(), 0ul); | |
| // But shouldn't produce any more messages as the stream is paused. | |
| EXPECT_FALSE(q.Produce(kNow, kFragmentSize).has_value()); | |
| } | |
| TEST(RRSendQueueTest, CommittingResetsSSN) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(50); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.ssn, SSN(0)); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.ssn, SSN(1)); | |
| q.PrepareResetStream(StreamID(1)); | |
| // Buffered | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_TRUE(q.HasStreamsReadyToBeReset()); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(StreamID(1))); | |
| q.CommitResetStreams(); | |
| std::optional<SendQueue::DataToSend> chunk_three = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_three.has_value()); | |
| EXPECT_EQ(chunk_three->data.ssn, SSN(0)); | |
| } | |
| TEST(RRSendQueueTest, CommittingDoesNotResetMessageId) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(50); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.ssn, SSN(0)); | |
| EXPECT_EQ(chunk1.message_id, OutgoingMessageId(0)); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.ssn, SSN(1)); | |
| EXPECT_EQ(chunk2.message_id, OutgoingMessageId(1)); | |
| q.PrepareResetStream(kStreamID); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(kStreamID)); | |
| q.CommitResetStreams(); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.ssn, SSN(0)); | |
| EXPECT_EQ(chunk3.message_id, OutgoingMessageId(2)); | |
| } | |
| TEST(RRSendQueueTest, CommittingResetsSSNForPausedStreamsOnly) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(50); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, StreamID(1)); | |
| EXPECT_EQ(chunk_one->data.ssn, SSN(0)); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.stream_id, StreamID(3)); | |
| EXPECT_EQ(chunk_two->data.ssn, SSN(0)); | |
| q.PrepareResetStream(StreamID(3)); | |
| // Send two more messages - SID 3 will buffer, SID 1 will send. | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), kPPID, payload)); | |
| EXPECT_TRUE(q.HasStreamsReadyToBeReset()); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(StreamID(3))); | |
| q.CommitResetStreams(); | |
| std::optional<SendQueue::DataToSend> chunk_three = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_three.has_value()); | |
| EXPECT_EQ(chunk_three->data.stream_id, StreamID(1)); | |
| EXPECT_EQ(chunk_three->data.ssn, SSN(1)); | |
| std::optional<SendQueue::DataToSend> chunk_four = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_four.has_value()); | |
| EXPECT_EQ(chunk_four->data.stream_id, StreamID(3)); | |
| EXPECT_EQ(chunk_four->data.ssn, SSN(0)); | |
| } | |
| TEST(RRSendQueueTest, RollBackResumesSSN) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(50); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.ssn, SSN(0)); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_two.has_value()); | |
| EXPECT_EQ(chunk_two->data.ssn, SSN(1)); | |
| q.PrepareResetStream(StreamID(1)); | |
| // Buffered | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_TRUE(q.HasStreamsReadyToBeReset()); | |
| EXPECT_THAT(q.GetStreamsReadyToBeReset(), UnorderedElementsAre(StreamID(1))); | |
| q.RollbackResetStreams(); | |
| std::optional<SendQueue::DataToSend> chunk_three = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_three.has_value()); | |
| EXPECT_EQ(chunk_three->data.ssn, SSN(2)); | |
| } | |
| TEST(RRSendQueueTest, ReturnsFragmentsForOneMessageBeforeMovingToNext) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(200); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(2)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk4, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk4.data.stream_id, StreamID(2)); | |
| } | |
| TEST(RRSendQueueTest, ReturnsAlsoSmallFragmentsBeforeMovingToNext) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(kTwoFragmentPacketSize); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, payload)); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, | |
| SizeIs(kTwoFragmentPacketSize - kOneFragmentPacketSize)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(2)); | |
| EXPECT_THAT(chunk3.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk4, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk4.data.stream_id, StreamID(2)); | |
| EXPECT_THAT(chunk4.data.payload, | |
| SizeIs(kTwoFragmentPacketSize - kOneFragmentPacketSize)); | |
| } | |
| TEST(RRSendQueueTest, WillCycleInRoundRobinFashionBetweenStreams) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(1))); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(2))); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(3))); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(4))); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), kPPID, std::vector<uint8_t>(5))); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), kPPID, std::vector<uint8_t>(6))); | |
| q.Add(kNow, DcSctpMessage(StreamID(4), kPPID, std::vector<uint8_t>(7))); | |
| q.Add(kNow, DcSctpMessage(StreamID(4), kPPID, std::vector<uint8_t>(8))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(1)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(2)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(3)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(3)); | |
| EXPECT_THAT(chunk3.data.payload, SizeIs(5)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk4, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk4.data.stream_id, StreamID(4)); | |
| EXPECT_THAT(chunk4.data.payload, SizeIs(7)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk5, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk5.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk5.data.payload, SizeIs(2)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk6, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk6.data.stream_id, StreamID(2)); | |
| EXPECT_THAT(chunk6.data.payload, SizeIs(4)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk7, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk7.data.stream_id, StreamID(3)); | |
| EXPECT_THAT(chunk7.data.payload, SizeIs(6)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk8, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk8.data.stream_id, StreamID(4)); | |
| EXPECT_THAT(chunk8.data.payload, SizeIs(8)); | |
| } | |
| TEST(RRSendQueueTest, DoesntTriggerOnBufferedAmountLowWhenSetToZero) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 0u); | |
| } | |
| TEST(RRSendQueueTest, TriggersOnBufferedAmountAtZeroLowWhenSent) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(1))); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 1u); | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(1)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 0u); | |
| } | |
| TEST(RRSendQueueTest, WillRetriggerOnBufferedAmountLowIfAddingMore) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(1))); | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(1)); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(1))); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 1u); | |
| // Should now trigger again, as buffer_amount went above the threshold. | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(1)); | |
| } | |
| TEST(RRSendQueueTest, OnlyTriggersWhenTransitioningFromAboveToBelowOrEqual) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 1000); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(10))); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 10u); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(10)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 0u); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(20))); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 20u); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(20)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 0u); | |
| } | |
| TEST(RRSendQueueTest, WillTriggerOnBufferedAmountLowSetAboveZero) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 700); | |
| std::vector<uint8_t> payload(1000); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, payload)); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 900u); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 800u); | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk3.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 700u); | |
| // Doesn't trigger when reducing even further. | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk4, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk3.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 600u); | |
| } | |
| TEST(RRSendQueueTest, WillRetriggerOnBufferedAmountLowSetAboveZero) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 700); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(1000))); | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, 400)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(400)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 600u); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(200))); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 800u); | |
| // Will trigger again, as it went above the limit. | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, 200)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(200)); | |
| EXPECT_EQ(q.buffered_amount(StreamID(1)), 600u); | |
| } | |
| TEST(RRSendQueueTest, TriggersOnBufferedAmountLowOnThresholdChanged) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(100))); | |
| // Modifying the threshold, still under buffered_amount, should not trigger. | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 50); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 99); | |
| // When the threshold reaches buffered_amount, it will trigger. | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 100); | |
| // But not when it's set low again. | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 50); | |
| // But it will trigger when it overshoots. | |
| EXPECT_CALL(cb, OnBufferedAmountLow(StreamID(1))); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 150); | |
| // But not when it's set low again. | |
| EXPECT_CALL(cb, OnBufferedAmountLow).Times(0); | |
| q.SetBufferedAmountLowThreshold(StreamID(1), 0); | |
| } | |
| TEST(RRSendQueueTest, OnTotalBufferedAmountLowDoesNotTriggerOnBufferFillingUp) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnTotalBufferedAmountLow).Times(0); | |
| std::vector<uint8_t> payload(kBufferedAmountLowThreshold - 1); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size()); | |
| // Will not trigger if going above but never below. | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, | |
| std::vector<uint8_t>(kOneFragmentPacketSize))); | |
| } | |
| TEST(RRSendQueueTest, TriggersOnTotalBufferedAmountLowWhenCrossing) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_CALL(cb, OnTotalBufferedAmountLow).Times(0); | |
| std::vector<uint8_t> payload(kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload)); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size()); | |
| // Reaches it. | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, std::vector<uint8_t>(1))); | |
| // Drain it a bit - will trigger. | |
| EXPECT_CALL(cb, OnTotalBufferedAmountLow).Times(1); | |
| std::optional<SendQueue::DataToSend> chunk_two = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| } | |
| TEST(RRSendQueueTest, WillStayInAStreamAsLongAsThatMessageIsSending) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.Add(kNow, DcSctpMessage(StreamID(5), kPPID, std::vector<uint8_t>(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk1, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk1.data.stream_id, StreamID(5)); | |
| EXPECT_THAT(chunk1.data.payload, SizeIs(1)); | |
| // Next, it should pick a different stream. | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, | |
| std::vector<uint8_t>(kOneFragmentPacketSize * 2))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk2, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk2.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk2.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| // It should still stay on the Stream1 now, even if might be tempted to switch | |
| // to this stream, as it's the stream following 5. | |
| q.Add(kNow, DcSctpMessage(StreamID(6), kPPID, std::vector<uint8_t>(1))); | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk3, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk3.data.stream_id, StreamID(1)); | |
| EXPECT_THAT(chunk3.data.payload, SizeIs(kOneFragmentPacketSize)); | |
| // After stream id 1 is complete, it's time to do stream 6. | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk4, | |
| q.Produce(kNow, kOneFragmentPacketSize)); | |
| EXPECT_EQ(chunk4.data.stream_id, StreamID(6)); | |
| EXPECT_THAT(chunk4.data.payload, SizeIs(1)); | |
| EXPECT_FALSE(q.Produce(kNow, kOneFragmentPacketSize).has_value()); | |
| } | |
| TEST(RRSendQueueTest, StreamsHaveInitialPriority) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| EXPECT_EQ(q.GetStreamPriority(StreamID(1)), kDefaultPriority); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(40))); | |
| EXPECT_EQ(q.GetStreamPriority(StreamID(2)), kDefaultPriority); | |
| } | |
| TEST(RRSendQueueTest, CanChangeStreamPriority) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.SetStreamPriority(StreamID(1), StreamPriority(42)); | |
| EXPECT_EQ(q.GetStreamPriority(StreamID(1)), StreamPriority(42)); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(40))); | |
| q.SetStreamPriority(StreamID(2), StreamPriority(42)); | |
| EXPECT_EQ(q.GetStreamPriority(StreamID(2)), StreamPriority(42)); | |
| } | |
| TEST(RRSendQueueTest, WillHandoverPriority) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.SetStreamPriority(StreamID(1), StreamPriority(42)); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(40))); | |
| q.SetStreamPriority(StreamID(2), StreamPriority(42)); | |
| DcSctpSocketHandoverState state; | |
| q.AddHandoverState(state); | |
| RRSendQueue q2("log: ", &cb, kMtu, kDefaultPriority, | |
| kBufferedAmountLowThreshold); | |
| q2.RestoreFromState(state); | |
| EXPECT_EQ(q2.GetStreamPriority(StreamID(1)), StreamPriority(42)); | |
| EXPECT_EQ(q2.GetStreamPriority(StreamID(2)), StreamPriority(42)); | |
| } | |
| TEST(RRSendQueueTest, WillSendMessagesByPrio) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| q.EnableMessageInterleaving(true); | |
| q.SetStreamPriority(StreamID(1), StreamPriority(10)); | |
| q.SetStreamPriority(StreamID(2), StreamPriority(20)); | |
| q.SetStreamPriority(StreamID(3), StreamPriority(30)); | |
| q.Add(kNow, DcSctpMessage(StreamID(1), kPPID, std::vector<uint8_t>(40))); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, std::vector<uint8_t>(20))); | |
| q.Add(kNow, DcSctpMessage(StreamID(3), kPPID, std::vector<uint8_t>(10))); | |
| std::vector<uint16_t> expected_streams = {3, 2, 2, 1, 1, 1, 1}; | |
| for (uint16_t stream_num : expected_streams) { | |
| ASSERT_HAS_VALUE_AND_ASSIGN(SendQueue::DataToSend chunk, | |
| q.Produce(kNow, 10)); | |
| EXPECT_EQ(chunk.data.stream_id, StreamID(stream_num)); | |
| } | |
| EXPECT_FALSE(q.Produce(kNow, 1).has_value()); | |
| } | |
| TEST(RRSendQueueTest, WillSendLifecycleExpireWhenExpiredInSendQueue) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(kOneFragmentPacketSize); | |
| q.Add(kNow, DcSctpMessage(StreamID(2), kPPID, payload), | |
| SendOptions{.lifetime = DurationMs(1000), | |
| .lifecycle_id = LifecycleId(1)}); | |
| EXPECT_CALL(cb, OnLifecycleMessageExpired(LifecycleId(1), | |
| /*maybe_delivered=*/false)); | |
| EXPECT_CALL(cb, OnLifecycleEnd(LifecycleId(1))); | |
| EXPECT_FALSE(q.Produce(kNow + TimeDelta::Millis(1001), kOneFragmentPacketSize) | |
| .has_value()); | |
| } | |
| TEST(RRSendQueueTest, WillSendLifecycleExpireWhenDiscardingDuringPause) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(120); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload), | |
| SendOptions{.lifecycle_id = LifecycleId(1)}); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload), | |
| SendOptions{.lifecycle_id = LifecycleId(2)}); | |
| std::optional<SendQueue::DataToSend> chunk_one = q.Produce(kNow, 50); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_EQ(q.total_buffered_amount(), 2 * payload.size() - 50); | |
| EXPECT_CALL(cb, OnLifecycleMessageExpired(LifecycleId(2), | |
| /*maybe_delivered=*/false)); | |
| EXPECT_CALL(cb, OnLifecycleEnd(LifecycleId(2))); | |
| q.PrepareResetStream(StreamID(1)); | |
| EXPECT_EQ(q.total_buffered_amount(), payload.size() - 50); | |
| } | |
| TEST(RRSendQueueTest, WillSendLifecycleExpireWhenDiscardingExplicitly) { | |
| testing::NiceMock<MockDcSctpSocketCallbacks> cb; | |
| RRSendQueue q("", &cb, kMtu, kDefaultPriority, kBufferedAmountLowThreshold); | |
| std::vector<uint8_t> payload(kOneFragmentPacketSize + 20); | |
| q.Add(kNow, DcSctpMessage(kStreamID, kPPID, payload), | |
| SendOptions{.lifecycle_id = LifecycleId(1)}); | |
| std::optional<SendQueue::DataToSend> chunk_one = | |
| q.Produce(kNow, kOneFragmentPacketSize); | |
| ASSERT_TRUE(chunk_one.has_value()); | |
| EXPECT_FALSE(chunk_one->data.is_end); | |
| EXPECT_EQ(chunk_one->data.stream_id, kStreamID); | |
| EXPECT_CALL(cb, OnLifecycleMessageExpired(LifecycleId(1), | |
| /*maybe_delivered=*/false)); | |
| EXPECT_CALL(cb, OnLifecycleEnd(LifecycleId(1))); | |
| q.Discard(chunk_one->data.stream_id, chunk_one->message_id); | |
| } | |
| } // namespace | |
| } // namespace dcsctp |