webrtc_m130/net/dcsctp/rx/reassembly_queue_test.cc

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

394 lines
15 KiB
C++
Raw Permalink Normal View History

/*
* 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/common/handover_testing.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;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
using SkippedStream = AnyForwardTsnChunk::SkippedStream;
// 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, 6> kSixBytePayload = {1, 2, 3, 4, 5, 6};
static constexpr std::array<uint8_t, 8> kMediumPayload1 = {1, 2, 3, 4,
5, 6, 7, 8};
static constexpr std::array<uint8_t, 8> kMediumPayload2 = {9, 10, 11, 12,
13, 14, 15, 16};
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: ", kBufferSize);
EXPECT_FALSE(reasm.HasMessages());
EXPECT_EQ(reasm.queued_bytes(), 0u);
}
TEST_F(ReassemblyQueueTest, SingleUnorderedChunkMessage) {
ReassemblyQueue reasm("log: ", 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: ", 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: ", 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: ", 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: ", 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: ", 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());
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.HandleForwardTsn(TSN(13), std::vector<SkippedStream>());
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: ", 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());
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.HandleForwardTsn(
TSN(13), std::vector<SkippedStream>({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: ", 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());
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.HandleForwardTsn(
TSN(13), std::vector<SkippedStream>({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, NotReadyForHandoverWhenResetStreamIsDeferred) {
ReassemblyQueue reasm("log: ", kBufferSize);
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.Add(TSN(10), gen_.Ordered({1, 2, 3, 4}, "BE", {.mid = MID(0)}));
reasm.Add(TSN(11), gen_.Ordered({1, 2, 3, 4}, "BE", {.mid = MID(1)}));
EXPECT_THAT(reasm.FlushMessages(), SizeIs(2));
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.EnterDeferredReset(TSN(12), std::vector<StreamID>({StreamID(1)}));
EXPECT_EQ(reasm.GetHandoverReadiness(),
HandoverReadinessStatus().Add(
HandoverUnreadinessReason::kStreamResetDeferred));
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.Add(TSN(12), gen_.Ordered({1, 2, 3, 4}, "BE", {.mid = MID(2)}));
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.ResetStreamsAndLeaveDeferredReset(std::vector<StreamID>({StreamID(1)}));
EXPECT_EQ(reasm.GetHandoverReadiness(), HandoverReadinessStatus());
}
TEST_F(ReassemblyQueueTest, HandoverInInitialState) {
ReassemblyQueue reasm1("log: ", kBufferSize);
EXPECT_EQ(reasm1.GetHandoverReadiness(), HandoverReadinessStatus());
DcSctpSocketHandoverState state;
reasm1.AddHandoverState(state);
g_handover_state_transformer_for_test(&state);
ReassemblyQueue reasm2("log: ", kBufferSize,
/*use_message_interleaving=*/false);
reasm2.RestoreFromState(state);
reasm2.Add(TSN(10), gen_.Ordered({1, 2, 3, 4}, "BE"));
EXPECT_THAT(reasm2.FlushMessages(), SizeIs(1));
}
TEST_F(ReassemblyQueueTest, HandoverAfterHavingAssembedOneMessage) {
ReassemblyQueue reasm1("log: ", kBufferSize);
reasm1.Add(TSN(10), gen_.Ordered({1, 2, 3, 4}, "BE"));
EXPECT_THAT(reasm1.FlushMessages(), SizeIs(1));
EXPECT_EQ(reasm1.GetHandoverReadiness(), HandoverReadinessStatus());
DcSctpSocketHandoverState state;
reasm1.AddHandoverState(state);
g_handover_state_transformer_for_test(&state);
ReassemblyQueue reasm2("log: ", kBufferSize,
/*use_message_interleaving=*/false);
reasm2.RestoreFromState(state);
reasm2.Add(TSN(11), gen_.Ordered({1, 2, 3, 4}, "BE"));
EXPECT_THAT(reasm2.FlushMessages(), SizeIs(1));
}
TEST_F(ReassemblyQueueTest, SingleUnorderedChunkMessageInRfc8260) {
ReassemblyQueue reasm("log: ", kBufferSize,
/*use_message_interleaving=*/true);
reasm.Add(TSN(10), Data(StreamID(1), SSN(0), MID(0), FSN(0), kPPID,
{1, 2, 3, 4}, Data::IsBeginning(true),
Data::IsEnd(true), IsUnordered(true)));
EXPECT_EQ(reasm.queued_bytes(), 0u);
EXPECT_TRUE(reasm.HasMessages());
EXPECT_THAT(reasm.FlushMessages(),
ElementsAre(SctpMessageIs(kStreamID, kPPID, kShortPayload)));
}
TEST_F(ReassemblyQueueTest, TwoInterleavedChunks) {
ReassemblyQueue reasm("log: ", kBufferSize,
/*use_message_interleaving=*/true);
reasm.Add(TSN(10), Data(StreamID(1), SSN(0), MID(0), FSN(0), kPPID,
{1, 2, 3, 4}, Data::IsBeginning(true),
Data::IsEnd(false), IsUnordered(true)));
reasm.Add(TSN(11), Data(StreamID(2), SSN(0), MID(0), FSN(0), kPPID,
{9, 10, 11, 12}, Data::IsBeginning(true),
Data::IsEnd(false), IsUnordered(true)));
EXPECT_EQ(reasm.queued_bytes(), 8u);
reasm.Add(TSN(12), Data(StreamID(1), SSN(0), MID(0), FSN(1), kPPID,
{5, 6, 7, 8}, Data::IsBeginning(false),
Data::IsEnd(true), IsUnordered(true)));
EXPECT_EQ(reasm.queued_bytes(), 4u);
reasm.Add(TSN(13), Data(StreamID(2), SSN(0), MID(0), FSN(1), kPPID,
{13, 14, 15, 16}, Data::IsBeginning(false),
Data::IsEnd(true), IsUnordered(true)));
EXPECT_EQ(reasm.queued_bytes(), 0u);
EXPECT_TRUE(reasm.HasMessages());
EXPECT_THAT(reasm.FlushMessages(),
ElementsAre(SctpMessageIs(StreamID(1), kPPID, kMediumPayload1),
SctpMessageIs(StreamID(2), kPPID, kMediumPayload2)));
}
TEST_F(ReassemblyQueueTest, UnorderedInterleavedMessagesAllPermutations) {
std::vector<int> indexes = {0, 1, 2, 3, 4, 5};
TSN tsns[] = {TSN(10), TSN(11), TSN(12), TSN(13), TSN(14), TSN(15)};
StreamID stream_ids[] = {StreamID(1), StreamID(2), StreamID(1),
StreamID(1), StreamID(2), StreamID(2)};
FSN fsns[] = {FSN(0), FSN(0), FSN(1), FSN(2), FSN(1), FSN(2)};
rtc::ArrayView<const uint8_t> payload(kSixBytePayload);
do {
ReassemblyQueue reasm("log: ", kBufferSize,
/*use_message_interleaving=*/true);
for (int i : indexes) {
auto span = payload.subview(*fsns[i] * 2, 2);
Data::IsBeginning is_beginning(fsns[i] == FSN(0));
Data::IsEnd is_end(fsns[i] == FSN(2));
reasm.Add(tsns[i], Data(stream_ids[i], SSN(0), MID(0), fsns[i], kPPID,
std::vector<uint8_t>(span.begin(), span.end()),
is_beginning, is_end, IsUnordered(true)));
}
EXPECT_TRUE(reasm.HasMessages());
EXPECT_THAT(reasm.FlushMessages(),
UnorderedElementsAre(
SctpMessageIs(StreamID(1), kPPID, kSixBytePayload),
SctpMessageIs(StreamID(2), kPPID, kSixBytePayload)));
EXPECT_EQ(reasm.queued_bytes(), 0u);
} while (std::next_permutation(std::begin(indexes), std::end(indexes)));
}
TEST_F(ReassemblyQueueTest, IForwardTSNRemoveALotOrdered) {
ReassemblyQueue reasm("log: ", kBufferSize,
/*use_message_interleaving=*/true);
reasm.Add(TSN(10), gen_.Ordered({1}, "B"));
gen_.Ordered({2}, "");
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"));
ASSERT_FALSE(reasm.HasMessages());
EXPECT_EQ(reasm.queued_bytes(), 7u);
dcsctp: Reset synchronously with incoming request When a sender has requested a stream to be reset, and the last sender assigned TSN hasn't been received yet, the receiver will enter deferred reset mode, where it will store any data chunks received after that given TSN, and replay those later, when the stream has been reset. Before this CL, leaving deferred mode was done as soon as the sender's last assigned TSN was received. That's actually not how the RFC describes the process[1], but was done that way to properly handle some sequences of RE-CONFIG and FORWARD-TSN. But after having read the RFCs again, and realizing that whenever RFC6525 mention "any data arriving", this also applies to any FORWARD-TSN[2] - it's better to reset streams synchronously with the incoming requests, and defer not just DATA past the sender last assigned TSN, but also any FORWARD-TSN after that TSN. This mostly simplifies the code and is mostly a refactoring, but most importantly aligns it with how the resetting procedure is explained in the RFC. It also fixes two bugs: * It defers FORWARD-TSN *as well as* DATA chunks with a TSN later than the sender's last assigned TSN - see test case. The old implementation tried to handle that by exiting the deferred reset processing as soon as it reached the sender's last assigned TSN, but it didn't manage to do that in all cases. * It only defers DATA chunks for streams that are to be reset, not all DATA chunks with a TSN > sender's last assigned TSN. This was missed in the old implementation, but as it's now implemented strictly according to the RFC, this was now done. [1] https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2 [2] RFC6525 cover stream resetting, and RFC3758 cover FORWARD-TSN, and the combination of these is not covered in the RFCs. Bug: webrtc:14600 Change-Id: Ief878b755291b9c923aa6fb4317b0f5c00231df4 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322623 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Victor Boivie <boivie@webrtc.org> Cr-Commit-Position: refs/heads/main@{#40889}
2023-09-27 18:01:18 +02:00
reasm.HandleForwardTsn(TSN(13), std::vector<SkippedStream>({SkippedStream(
IsUnordered(false), kStreamID, MID(0))}));
EXPECT_EQ(reasm.queued_bytes(), 0u);
// The lost chunk comes, but too late.
ASSERT_TRUE(reasm.HasMessages());
EXPECT_THAT(reasm.FlushMessages(),
ElementsAre(SctpMessageIs(kStreamID, kPPID, kMessage2Payload)));
}
} // namespace
} // namespace dcsctp