dcsctp: Add Reassembly Queue
The Reassembly Queue receives fragmented messages (DATA or I-DATA chunks) and - with help of stream reassemblers - will reassemble these fragments into messages, which will be delivered to the client. It also handle partial reliability (FORWARD-TSN) and stream resetting. To avoid a DoS attack vector, where a sender can send fragments in a way that the reassembly queue will never succeed to reassemble a message and use all available memory, the ReassemblyQueue has a maximum size. Bug: webrtc:12614 Change-Id: Ibb084fecd240d4c414e096579244f8f5ee46914e Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/214043 Commit-Queue: Victor Boivie <boivie@webrtc.org> Reviewed-by: Tommi <tommi@webrtc.org> Cr-Commit-Position: refs/heads/master@{#33678}
This commit is contained in:
committed by
Commit Bot
parent
8a13d2ca9f
commit
cb70aa7e05
298
net/dcsctp/rx/reassembly_queue_test.cc
Normal file
298
net/dcsctp/rx/reassembly_queue_test.cc
Normal file
@ -0,0 +1,298 @@
|
||||
/*
|
||||
* 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/rx/reassembly_queue.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <vector>
|
||||
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/public/dcsctp_message.h"
|
||||
#include "net/dcsctp/public/types.h"
|
||||
#include "net/dcsctp/testing/data_generator.h"
|
||||
#include "rtc_base/gunit.h"
|
||||
#include "test/gmock.h"
|
||||
|
||||
namespace dcsctp {
|
||||
namespace {
|
||||
using ::testing::ElementsAre;
|
||||
|
||||
// The default maximum size of the Reassembly Queue.
|
||||
static constexpr size_t kBufferSize = 10000;
|
||||
|
||||
static constexpr StreamID kStreamID(1);
|
||||
static constexpr SSN kSSN(0);
|
||||
static constexpr MID kMID(0);
|
||||
static constexpr FSN kFSN(0);
|
||||
static constexpr PPID kPPID(53);
|
||||
|
||||
static constexpr std::array<uint8_t, 4> kShortPayload = {1, 2, 3, 4};
|
||||
static constexpr std::array<uint8_t, 4> kMessage2Payload = {5, 6, 7, 8};
|
||||
static constexpr std::array<uint8_t, 16> kLongPayload = {
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
|
||||
|
||||
MATCHER_P3(SctpMessageIs, stream_id, ppid, expected_payload, "") {
|
||||
if (arg.stream_id() != stream_id) {
|
||||
*result_listener << "the stream_id is " << *arg.stream_id();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (arg.ppid() != ppid) {
|
||||
*result_listener << "the ppid is " << *arg.ppid();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::vector<uint8_t>(arg.payload().begin(), arg.payload().end()) !=
|
||||
std::vector<uint8_t>(expected_payload.begin(), expected_payload.end())) {
|
||||
*result_listener << "the payload is wrong";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class ReassemblyQueueTest : public testing::Test {
|
||||
protected:
|
||||
ReassemblyQueueTest() {}
|
||||
DataGenerator gen_;
|
||||
};
|
||||
|
||||
TEST_F(ReassemblyQueueTest, EmptyQueue) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, SingleUnorderedChunkMessage) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kShortPayload)));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, LargeUnorderedChunkAllPermutations) {
|
||||
std::vector<uint32_t> tsns = {10, 11, 12, 13};
|
||||
rtc::ArrayView<const uint8_t> payload(kLongPayload);
|
||||
do {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
|
||||
for (size_t i = 0; i < tsns.size(); i++) {
|
||||
auto span = payload.subview((tsns[i] - 10) * 4, 4);
|
||||
Data::IsBeginning is_beginning(tsns[i] == 10);
|
||||
Data::IsEnd is_end(tsns[i] == 13);
|
||||
|
||||
reasm.Add(TSN(tsns[i]),
|
||||
Data(kStreamID, kSSN, kMID, kFSN, kPPID,
|
||||
std::vector<uint8_t>(span.begin(), span.end()),
|
||||
is_beginning, is_end, IsUnordered(false)));
|
||||
if (i < 3) {
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
} else {
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kLongPayload)));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
}
|
||||
}
|
||||
} while (std::next_permutation(std::begin(tsns), std::end(tsns)));
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, SingleOrderedChunkMessage) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Ordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kShortPayload)));
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ManySmallOrderedMessages) {
|
||||
std::vector<uint32_t> tsns = {10, 11, 12, 13};
|
||||
rtc::ArrayView<const uint8_t> payload(kLongPayload);
|
||||
do {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
for (size_t i = 0; i < tsns.size(); i++) {
|
||||
auto span = payload.subview((tsns[i] - 10) * 4, 4);
|
||||
Data::IsBeginning is_beginning(true);
|
||||
Data::IsEnd is_end(true);
|
||||
|
||||
SSN ssn(static_cast<uint16_t>(tsns[i] - 10));
|
||||
reasm.Add(TSN(tsns[i]),
|
||||
Data(kStreamID, ssn, kMID, kFSN, kPPID,
|
||||
std::vector<uint8_t>(span.begin(), span.end()),
|
||||
is_beginning, is_end, IsUnordered(false)));
|
||||
}
|
||||
EXPECT_THAT(
|
||||
reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, payload.subview(0, 4)),
|
||||
SctpMessageIs(kStreamID, kPPID, payload.subview(4, 4)),
|
||||
SctpMessageIs(kStreamID, kPPID, payload.subview(8, 4)),
|
||||
SctpMessageIs(kStreamID, kPPID, payload.subview(12, 4))));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
} while (std::next_permutation(std::begin(tsns), std::end(tsns)));
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, RetransmissionInLargeOrdered) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Ordered({1}, "B"));
|
||||
reasm.Add(TSN(12), gen_.Ordered({3}));
|
||||
reasm.Add(TSN(13), gen_.Ordered({4}));
|
||||
reasm.Add(TSN(14), gen_.Ordered({5}));
|
||||
reasm.Add(TSN(15), gen_.Ordered({6}));
|
||||
reasm.Add(TSN(16), gen_.Ordered({7}));
|
||||
reasm.Add(TSN(17), gen_.Ordered({8}));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 7u);
|
||||
|
||||
// lost and retransmitted
|
||||
reasm.Add(TSN(11), gen_.Ordered({2}));
|
||||
reasm.Add(TSN(18), gen_.Ordered({9}));
|
||||
reasm.Add(TSN(19), gen_.Ordered({10}));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 10u);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
|
||||
reasm.Add(TSN(20), gen_.Ordered({11, 12, 13, 14, 15, 16}, "E"));
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kLongPayload)));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ForwardTSNRemoveUnordered) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Unordered({1}, "B"));
|
||||
reasm.Add(TSN(12), gen_.Unordered({3}));
|
||||
reasm.Add(TSN(13), gen_.Unordered({4}, "E"));
|
||||
|
||||
reasm.Add(TSN(14), gen_.Unordered({5}, "B"));
|
||||
reasm.Add(TSN(15), gen_.Unordered({6}));
|
||||
reasm.Add(TSN(17), gen_.Unordered({8}, "E"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 6u);
|
||||
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
|
||||
reasm.Handle(ForwardTsnChunk(TSN(13), {}));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 3u);
|
||||
|
||||
// The lost chunk comes, but too late.
|
||||
reasm.Add(TSN(11), gen_.Unordered({2}));
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
EXPECT_EQ(reasm.queued_bytes(), 3u);
|
||||
|
||||
// The second lost chunk comes, message is assembled.
|
||||
reasm.Add(TSN(16), gen_.Unordered({7}));
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ForwardTSNRemoveOrdered) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Ordered({1}, "B"));
|
||||
reasm.Add(TSN(12), gen_.Ordered({3}));
|
||||
reasm.Add(TSN(13), gen_.Ordered({4}, "E"));
|
||||
|
||||
reasm.Add(TSN(14), gen_.Ordered({5}, "B"));
|
||||
reasm.Add(TSN(15), gen_.Ordered({6}));
|
||||
reasm.Add(TSN(16), gen_.Ordered({7}));
|
||||
reasm.Add(TSN(17), gen_.Ordered({8}, "E"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 7u);
|
||||
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
|
||||
reasm.Handle(ForwardTsnChunk(
|
||||
TSN(13), {ForwardTsnChunk::SkippedStream(kStreamID, kSSN)}));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
|
||||
// The lost chunk comes, but too late.
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kMessage2Payload)));
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ForwardTSNRemoveALotOrdered) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Ordered({1}, "B"));
|
||||
reasm.Add(TSN(12), gen_.Ordered({3}));
|
||||
reasm.Add(TSN(13), gen_.Ordered({4}, "E"));
|
||||
|
||||
reasm.Add(TSN(15), gen_.Ordered({5}, "B"));
|
||||
reasm.Add(TSN(16), gen_.Ordered({6}));
|
||||
reasm.Add(TSN(17), gen_.Ordered({7}));
|
||||
reasm.Add(TSN(18), gen_.Ordered({8}, "E"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 7u);
|
||||
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
|
||||
reasm.Handle(ForwardTsnChunk(
|
||||
TSN(13), {ForwardTsnChunk::SkippedStream(kStreamID, kSSN)}));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
|
||||
// The lost chunk comes, but too late.
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kMessage2Payload)));
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ShouldntDeliverMessagesBeforeInitialTsn) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(5), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ShouldntRedeliverUnorderedMessages) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kShortPayload)));
|
||||
reasm.Add(TSN(10), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ShouldntRedeliverUnorderedMessagesReallyUnordered) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Add(TSN(10), gen_.Unordered({1, 2, 3, 4}, "B"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 4u);
|
||||
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
|
||||
reasm.Add(TSN(12), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 4u);
|
||||
EXPECT_TRUE(reasm.HasMessages());
|
||||
|
||||
EXPECT_THAT(reasm.FlushMessages(),
|
||||
ElementsAre(SctpMessageIs(kStreamID, kPPID, kShortPayload)));
|
||||
reasm.Add(TSN(12), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 4u);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
}
|
||||
|
||||
TEST_F(ReassemblyQueueTest, ShouldntDeliverBeforeForwardedTsn) {
|
||||
ReassemblyQueue reasm("log: ", TSN(10), kBufferSize);
|
||||
reasm.Handle(ForwardTsnChunk(TSN(12), {}));
|
||||
|
||||
reasm.Add(TSN(12), gen_.Unordered({1, 2, 3, 4}, "BE"));
|
||||
EXPECT_EQ(reasm.queued_bytes(), 0u);
|
||||
EXPECT_FALSE(reasm.HasMessages());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace dcsctp
|
||||
Reference in New Issue
Block a user