Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
387
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/data_tracker.cc
Normal file
387
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/data_tracker.cc
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* 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/data_tracker.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/sack_chunk.h"
|
||||
#include "net/dcsctp/timer/timer.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/strings/string_builder.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
constexpr size_t DataTracker::kMaxDuplicateTsnReported;
|
||||
constexpr size_t DataTracker::kMaxGapAckBlocksReported;
|
||||
|
||||
bool DataTracker::AdditionalTsnBlocks::Add(UnwrappedTSN tsn) {
|
||||
// Find any block to expand. It will look for any block that includes (also
|
||||
// when expanded) the provided `tsn`. It will return the block that is greater
|
||||
// than, or equal to `tsn`.
|
||||
auto it = absl::c_lower_bound(
|
||||
blocks_, tsn, [&](const TsnRange& elem, const UnwrappedTSN& t) {
|
||||
return elem.last.next_value() < t;
|
||||
});
|
||||
|
||||
if (it == blocks_.end()) {
|
||||
// No matching block found. There is no greater than, or equal block - which
|
||||
// means that this TSN is greater than any block. It can then be inserted at
|
||||
// the end.
|
||||
blocks_.emplace_back(tsn, tsn);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (tsn >= it->first && tsn <= it->last) {
|
||||
// It's already in this block.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (it->last.next_value() == tsn) {
|
||||
// This block can be expanded to the right, or merged with the next.
|
||||
auto next_it = it + 1;
|
||||
if (next_it != blocks_.end() && tsn.next_value() == next_it->first) {
|
||||
// Expanding it would make it adjacent to next block - merge those.
|
||||
it->last = next_it->last;
|
||||
blocks_.erase(next_it);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Expand to the right
|
||||
it->last = tsn;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (it->first == tsn.next_value()) {
|
||||
// This block can be expanded to the left. Merging to the left would've been
|
||||
// covered by the above "merge to the right". Both blocks (expand a
|
||||
// right-most block to the left and expand a left-most block to the right)
|
||||
// would match, but the left-most would be returned by std::lower_bound.
|
||||
RTC_DCHECK(it == blocks_.begin() || (it - 1)->last.next_value() != tsn);
|
||||
|
||||
// Expand to the left.
|
||||
it->first = tsn;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Need to create a new block in the middle.
|
||||
blocks_.emplace(it, tsn, tsn);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataTracker::AdditionalTsnBlocks::EraseTo(UnwrappedTSN tsn) {
|
||||
// Find the block that is greater than or equals `tsn`.
|
||||
auto it = absl::c_lower_bound(
|
||||
blocks_, tsn, [&](const TsnRange& elem, const UnwrappedTSN& t) {
|
||||
return elem.last < t;
|
||||
});
|
||||
|
||||
// The block that is found is greater or equal (or possibly ::end, when no
|
||||
// block is greater or equal). All blocks before this block can be safely
|
||||
// removed. the TSN might be within this block, so possibly truncate it.
|
||||
bool tsn_is_within_block = it != blocks_.end() && tsn >= it->first;
|
||||
blocks_.erase(blocks_.begin(), it);
|
||||
|
||||
if (tsn_is_within_block) {
|
||||
blocks_.front().first = tsn.next_value();
|
||||
}
|
||||
}
|
||||
|
||||
void DataTracker::AdditionalTsnBlocks::PopFront() {
|
||||
RTC_DCHECK(!blocks_.empty());
|
||||
blocks_.erase(blocks_.begin());
|
||||
}
|
||||
|
||||
bool DataTracker::IsTSNValid(TSN tsn) const {
|
||||
UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.PeekUnwrap(tsn);
|
||||
|
||||
// Note that this method doesn't return `false` for old DATA chunks, as those
|
||||
// are actually valid, and receiving those may affect the generated SACK
|
||||
// response (by setting "duplicate TSNs").
|
||||
|
||||
uint32_t difference =
|
||||
UnwrappedTSN::Difference(unwrapped_tsn, last_cumulative_acked_tsn_);
|
||||
if (difference > kMaxAcceptedOutstandingFragments) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataTracker::Observe(TSN tsn,
|
||||
AnyDataChunk::ImmediateAckFlag immediate_ack) {
|
||||
bool is_duplicate = false;
|
||||
UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(tsn);
|
||||
|
||||
// IsTSNValid must be called prior to calling this method.
|
||||
RTC_DCHECK(
|
||||
UnwrappedTSN::Difference(unwrapped_tsn, last_cumulative_acked_tsn_) <=
|
||||
kMaxAcceptedOutstandingFragments);
|
||||
|
||||
// Old chunk already seen before?
|
||||
if (unwrapped_tsn <= last_cumulative_acked_tsn_) {
|
||||
if (duplicate_tsns_.size() < kMaxDuplicateTsnReported) {
|
||||
duplicate_tsns_.insert(unwrapped_tsn.Wrap());
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc4960#section-6.2
|
||||
// "When a packet arrives with duplicate DATA chunk(s) and with no new DATA
|
||||
// chunk(s), the endpoint MUST immediately send a SACK with no delay. If a
|
||||
// packet arrives with duplicate DATA chunk(s) bundled with new DATA chunks,
|
||||
// the endpoint MAY immediately send a SACK."
|
||||
UpdateAckState(AckState::kImmediate, "duplicate data");
|
||||
is_duplicate = true;
|
||||
} else {
|
||||
if (unwrapped_tsn == last_cumulative_acked_tsn_.next_value()) {
|
||||
last_cumulative_acked_tsn_ = unwrapped_tsn;
|
||||
// The cumulative acked tsn may be moved even further, if a gap was
|
||||
// filled.
|
||||
if (!additional_tsn_blocks_.empty() &&
|
||||
additional_tsn_blocks_.front().first ==
|
||||
last_cumulative_acked_tsn_.next_value()) {
|
||||
last_cumulative_acked_tsn_ = additional_tsn_blocks_.front().last;
|
||||
additional_tsn_blocks_.PopFront();
|
||||
}
|
||||
} else {
|
||||
bool inserted = additional_tsn_blocks_.Add(unwrapped_tsn);
|
||||
if (!inserted) {
|
||||
// Already seen before.
|
||||
if (duplicate_tsns_.size() < kMaxDuplicateTsnReported) {
|
||||
duplicate_tsns_.insert(unwrapped_tsn.Wrap());
|
||||
}
|
||||
// https://datatracker.ietf.org/doc/html/rfc4960#section-6.2
|
||||
// "When a packet arrives with duplicate DATA chunk(s) and with no new
|
||||
// DATA chunk(s), the endpoint MUST immediately send a SACK with no
|
||||
// delay. If a packet arrives with duplicate DATA chunk(s) bundled with
|
||||
// new DATA chunks, the endpoint MAY immediately send a SACK."
|
||||
// No need to do this. SACKs are sent immediately on packet loss below.
|
||||
is_duplicate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc4960#section-6.7
|
||||
// "Upon the reception of a new DATA chunk, an endpoint shall examine the
|
||||
// continuity of the TSNs received. If the endpoint detects a gap in
|
||||
// the received DATA chunk sequence, it SHOULD send a SACK with Gap Ack
|
||||
// Blocks immediately. The data receiver continues sending a SACK after
|
||||
// receipt of each SCTP packet that doesn't fill the gap."
|
||||
if (!additional_tsn_blocks_.empty()) {
|
||||
UpdateAckState(AckState::kImmediate, "packet loss");
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc7053#section-5.2
|
||||
// "Upon receipt of an SCTP packet containing a DATA chunk with the I
|
||||
// bit set, the receiver SHOULD NOT delay the sending of the corresponding
|
||||
// SACK chunk, i.e., the receiver SHOULD immediately respond with the
|
||||
// corresponding SACK chunk."
|
||||
if (*immediate_ack) {
|
||||
UpdateAckState(AckState::kImmediate, "immediate-ack bit set");
|
||||
}
|
||||
|
||||
if (!seen_packet_) {
|
||||
// https://tools.ietf.org/html/rfc4960#section-5.1
|
||||
// "After the reception of the first DATA chunk in an association the
|
||||
// endpoint MUST immediately respond with a SACK to acknowledge the DATA
|
||||
// chunk."
|
||||
seen_packet_ = true;
|
||||
UpdateAckState(AckState::kImmediate, "first DATA chunk");
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc4960#section-6.2
|
||||
// "Specifically, an acknowledgement SHOULD be generated for at least
|
||||
// every second packet (not every second DATA chunk) received, and SHOULD be
|
||||
// generated within 200 ms of the arrival of any unacknowledged DATA chunk."
|
||||
if (ack_state_ == AckState::kIdle) {
|
||||
UpdateAckState(AckState::kBecomingDelayed, "received DATA when idle");
|
||||
} else if (ack_state_ == AckState::kDelayed) {
|
||||
UpdateAckState(AckState::kImmediate, "received DATA when already delayed");
|
||||
}
|
||||
return !is_duplicate;
|
||||
}
|
||||
|
||||
bool DataTracker::HandleForwardTsn(TSN new_cumulative_ack) {
|
||||
// ForwardTSN is sent to make the receiver (this socket) "forget" about partly
|
||||
// received (or not received at all) data, up until `new_cumulative_ack`.
|
||||
|
||||
UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(new_cumulative_ack);
|
||||
UnwrappedTSN prev_last_cum_ack_tsn = last_cumulative_acked_tsn_;
|
||||
|
||||
// Old chunk already seen before?
|
||||
if (unwrapped_tsn <= last_cumulative_acked_tsn_) {
|
||||
// https://tools.ietf.org/html/rfc3758#section-3.6
|
||||
// "Note, if the "New Cumulative TSN" value carried in the arrived
|
||||
// FORWARD TSN chunk is found to be behind or at the current cumulative TSN
|
||||
// point, the data receiver MUST treat this FORWARD TSN as out-of-date and
|
||||
// MUST NOT update its Cumulative TSN. The receiver SHOULD send a SACK to
|
||||
// its peer (the sender of the FORWARD TSN) since such a duplicate may
|
||||
// indicate the previous SACK was lost in the network."
|
||||
UpdateAckState(AckState::kImmediate,
|
||||
"FORWARD_TSN new_cumulative_tsn was behind");
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc3758#section-3.6
|
||||
// "When a FORWARD TSN chunk arrives, the data receiver MUST first update
|
||||
// its cumulative TSN point to the value carried in the FORWARD TSN chunk, and
|
||||
// then MUST further advance its cumulative TSN point locally if possible, as
|
||||
// shown by the following example..."
|
||||
|
||||
// The `new_cumulative_ack` will become the current
|
||||
// `last_cumulative_acked_tsn_`, and if there have been prior "gaps" that are
|
||||
// now overlapping with the new value, remove them.
|
||||
last_cumulative_acked_tsn_ = unwrapped_tsn;
|
||||
additional_tsn_blocks_.EraseTo(unwrapped_tsn);
|
||||
|
||||
// See if the `last_cumulative_acked_tsn_` can be moved even further:
|
||||
if (!additional_tsn_blocks_.empty() &&
|
||||
additional_tsn_blocks_.front().first ==
|
||||
last_cumulative_acked_tsn_.next_value()) {
|
||||
last_cumulative_acked_tsn_ = additional_tsn_blocks_.front().last;
|
||||
additional_tsn_blocks_.PopFront();
|
||||
}
|
||||
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "FORWARD_TSN, cum_ack_tsn="
|
||||
<< *prev_last_cum_ack_tsn.Wrap() << "->"
|
||||
<< *new_cumulative_ack << "->"
|
||||
<< *last_cumulative_acked_tsn_.Wrap();
|
||||
|
||||
// https://tools.ietf.org/html/rfc3758#section-3.6
|
||||
// "Any time a FORWARD TSN chunk arrives, for the purposes of sending a
|
||||
// SACK, the receiver MUST follow the same rules as if a DATA chunk had been
|
||||
// received (i.e., follow the delayed sack rules specified in ..."
|
||||
if (ack_state_ == AckState::kIdle) {
|
||||
UpdateAckState(AckState::kBecomingDelayed,
|
||||
"received FORWARD_TSN when idle");
|
||||
} else if (ack_state_ == AckState::kDelayed) {
|
||||
UpdateAckState(AckState::kImmediate,
|
||||
"received FORWARD_TSN when already delayed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
SackChunk DataTracker::CreateSelectiveAck(size_t a_rwnd) {
|
||||
// Note that in SCTP, the receiver side is allowed to discard received data
|
||||
// and signal that to the sender, but only chunks that have previously been
|
||||
// reported in the gap-ack-blocks. However, this implementation will never do
|
||||
// that. So this SACK produced is more like a NR-SACK as explained in
|
||||
// https://ieeexplore.ieee.org/document/4697037 and which there is an RFC
|
||||
// draft at https://tools.ietf.org/html/draft-tuexen-tsvwg-sctp-multipath-17.
|
||||
std::set<TSN> duplicate_tsns;
|
||||
duplicate_tsns_.swap(duplicate_tsns);
|
||||
|
||||
return SackChunk(last_cumulative_acked_tsn_.Wrap(), a_rwnd,
|
||||
CreateGapAckBlocks(), std::move(duplicate_tsns));
|
||||
}
|
||||
|
||||
std::vector<SackChunk::GapAckBlock> DataTracker::CreateGapAckBlocks() const {
|
||||
const auto& blocks = additional_tsn_blocks_.blocks();
|
||||
std::vector<SackChunk::GapAckBlock> gap_ack_blocks;
|
||||
gap_ack_blocks.reserve(std::min(blocks.size(), kMaxGapAckBlocksReported));
|
||||
for (size_t i = 0; i < blocks.size() && i < kMaxGapAckBlocksReported; ++i) {
|
||||
auto start_diff =
|
||||
UnwrappedTSN::Difference(blocks[i].first, last_cumulative_acked_tsn_);
|
||||
auto end_diff =
|
||||
UnwrappedTSN::Difference(blocks[i].last, last_cumulative_acked_tsn_);
|
||||
gap_ack_blocks.emplace_back(static_cast<uint16_t>(start_diff),
|
||||
static_cast<uint16_t>(end_diff));
|
||||
}
|
||||
|
||||
return gap_ack_blocks;
|
||||
}
|
||||
|
||||
bool DataTracker::ShouldSendAck(bool also_if_delayed) {
|
||||
if (ack_state_ == AckState::kImmediate ||
|
||||
(also_if_delayed && (ack_state_ == AckState::kBecomingDelayed ||
|
||||
ack_state_ == AckState::kDelayed))) {
|
||||
UpdateAckState(AckState::kIdle, "sending SACK");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DataTracker::will_increase_cum_ack_tsn(TSN tsn) const {
|
||||
UnwrappedTSN unwrapped = tsn_unwrapper_.PeekUnwrap(tsn);
|
||||
return unwrapped == last_cumulative_acked_tsn_.next_value();
|
||||
}
|
||||
|
||||
void DataTracker::ForceImmediateSack() {
|
||||
ack_state_ = AckState::kImmediate;
|
||||
}
|
||||
|
||||
void DataTracker::HandleDelayedAckTimerExpiry() {
|
||||
UpdateAckState(AckState::kImmediate, "delayed ack timer expired");
|
||||
}
|
||||
|
||||
void DataTracker::ObservePacketEnd() {
|
||||
if (ack_state_ == AckState::kBecomingDelayed) {
|
||||
UpdateAckState(AckState::kDelayed, "packet end");
|
||||
}
|
||||
}
|
||||
|
||||
void DataTracker::UpdateAckState(AckState new_state, absl::string_view reason) {
|
||||
if (new_state != ack_state_) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "State changed from "
|
||||
<< ToString(ack_state_) << " to "
|
||||
<< ToString(new_state) << " due to " << reason;
|
||||
if (ack_state_ == AckState::kDelayed) {
|
||||
delayed_ack_timer_.Stop();
|
||||
} else if (new_state == AckState::kDelayed) {
|
||||
delayed_ack_timer_.Start();
|
||||
}
|
||||
ack_state_ = new_state;
|
||||
}
|
||||
}
|
||||
|
||||
absl::string_view DataTracker::ToString(AckState ack_state) {
|
||||
switch (ack_state) {
|
||||
case AckState::kIdle:
|
||||
return "IDLE";
|
||||
case AckState::kBecomingDelayed:
|
||||
return "BECOMING_DELAYED";
|
||||
case AckState::kDelayed:
|
||||
return "DELAYED";
|
||||
case AckState::kImmediate:
|
||||
return "IMMEDIATE";
|
||||
}
|
||||
}
|
||||
|
||||
HandoverReadinessStatus DataTracker::GetHandoverReadiness() const {
|
||||
HandoverReadinessStatus status;
|
||||
if (!additional_tsn_blocks_.empty()) {
|
||||
status.Add(HandoverUnreadinessReason::kDataTrackerTsnBlocksPending);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void DataTracker::AddHandoverState(DcSctpSocketHandoverState& state) {
|
||||
state.rx.last_cumulative_acked_tsn = last_cumulative_acked_tsn().value();
|
||||
state.rx.seen_packet = seen_packet_;
|
||||
}
|
||||
|
||||
void DataTracker::RestoreFromState(const DcSctpSocketHandoverState& state) {
|
||||
// Validate that the component is in pristine state.
|
||||
RTC_DCHECK(additional_tsn_blocks_.empty());
|
||||
RTC_DCHECK(duplicate_tsns_.empty());
|
||||
RTC_DCHECK(!seen_packet_);
|
||||
|
||||
seen_packet_ = state.rx.seen_packet;
|
||||
last_cumulative_acked_tsn_ =
|
||||
tsn_unwrapper_.Unwrap(TSN(state.rx.last_cumulative_acked_tsn));
|
||||
}
|
||||
} // namespace dcsctp
|
||||
195
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/data_tracker.h
Normal file
195
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/data_tracker.h
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NET_DCSCTP_RX_DATA_TRACKER_H_
|
||||
#define NET_DCSCTP_RX_DATA_TRACKER_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/data_common.h"
|
||||
#include "net/dcsctp/packet/chunk/sack_chunk.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/public/dcsctp_handover_state.h"
|
||||
#include "net/dcsctp/timer/timer.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
// Keeps track of received DATA chunks and handles all logic for _when_ to
|
||||
// create SACKs and also _how_ to generate them.
|
||||
//
|
||||
// It only uses TSNs to track delivery and doesn't need to be aware of streams.
|
||||
//
|
||||
// SACKs are optimally sent every second packet on connections with no packet
|
||||
// loss. When packet loss is detected, it's sent for every packet. When SACKs
|
||||
// are not sent directly, a timer is used to send a SACK delayed (by RTO/2, or
|
||||
// 200ms, whatever is smallest).
|
||||
class DataTracker {
|
||||
public:
|
||||
// The maximum number of duplicate TSNs that will be reported in a SACK.
|
||||
static constexpr size_t kMaxDuplicateTsnReported = 20;
|
||||
// The maximum number of gap-ack-blocks that will be reported in a SACK.
|
||||
static constexpr size_t kMaxGapAckBlocksReported = 20;
|
||||
|
||||
// The maximum number of accepted in-flight DATA chunks. This indicates the
|
||||
// maximum difference from this buffer's last cumulative ack TSN, and any
|
||||
// received data. Data received beyond this limit will be dropped, which will
|
||||
// force the transmitter to send data that actually increases the last
|
||||
// cumulative acked TSN.
|
||||
static constexpr uint32_t kMaxAcceptedOutstandingFragments = 100000;
|
||||
|
||||
DataTracker(absl::string_view log_prefix,
|
||||
Timer* delayed_ack_timer,
|
||||
TSN peer_initial_tsn)
|
||||
: log_prefix_(log_prefix),
|
||||
seen_packet_(false),
|
||||
delayed_ack_timer_(*delayed_ack_timer),
|
||||
last_cumulative_acked_tsn_(
|
||||
tsn_unwrapper_.Unwrap(TSN(*peer_initial_tsn - 1))) {}
|
||||
|
||||
// Indicates if the provided TSN is valid. If this return false, the data
|
||||
// should be dropped and not added to any other buffers, which essentially
|
||||
// means that there is intentional packet loss.
|
||||
bool IsTSNValid(TSN tsn) const;
|
||||
|
||||
// Call for every incoming data chunk. Returns `true` if `tsn` was seen for
|
||||
// the first time, and `false` if it has been seen before (a duplicate `tsn`).
|
||||
bool Observe(TSN tsn,
|
||||
AnyDataChunk::ImmediateAckFlag immediate_ack =
|
||||
AnyDataChunk::ImmediateAckFlag(false));
|
||||
// Called at the end of processing an SCTP packet.
|
||||
void ObservePacketEnd();
|
||||
|
||||
// Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks. Indicates if the
|
||||
// chunk had any effect.
|
||||
bool HandleForwardTsn(TSN new_cumulative_ack);
|
||||
|
||||
// Indicates if a SACK should be sent. There may be other reasons to send a
|
||||
// SACK, but if this function indicates so, it should be sent as soon as
|
||||
// possible. Calling this function will make it clear a flag so that if it's
|
||||
// called again, it will probably return false.
|
||||
//
|
||||
// If the delayed ack timer is running, this method will return false _unless_
|
||||
// `also_if_delayed` is set to true. Then it will return true as well.
|
||||
bool ShouldSendAck(bool also_if_delayed = false);
|
||||
|
||||
// Returns the last cumulative ack TSN - the last seen data chunk's TSN
|
||||
// value before any packet loss was detected.
|
||||
TSN last_cumulative_acked_tsn() const {
|
||||
return TSN(last_cumulative_acked_tsn_.Wrap());
|
||||
}
|
||||
|
||||
bool IsLaterThanCumulativeAckedTsn(TSN tsn) const {
|
||||
return tsn_unwrapper_.PeekUnwrap(tsn) > last_cumulative_acked_tsn_;
|
||||
}
|
||||
|
||||
// Returns true if the received `tsn` would increase the cumulative ack TSN.
|
||||
bool will_increase_cum_ack_tsn(TSN tsn) const;
|
||||
|
||||
// Forces `ShouldSendSack` to return true.
|
||||
void ForceImmediateSack();
|
||||
|
||||
// Note that this will clear `duplicates_`, so every SackChunk that is
|
||||
// consumed must be sent.
|
||||
SackChunk CreateSelectiveAck(size_t a_rwnd);
|
||||
|
||||
void HandleDelayedAckTimerExpiry();
|
||||
|
||||
HandoverReadinessStatus GetHandoverReadiness() const;
|
||||
|
||||
void AddHandoverState(DcSctpSocketHandoverState& state);
|
||||
void RestoreFromState(const DcSctpSocketHandoverState& state);
|
||||
|
||||
private:
|
||||
enum class AckState {
|
||||
// No need to send an ACK.
|
||||
kIdle,
|
||||
|
||||
// Has received data chunks (but not yet end of packet).
|
||||
kBecomingDelayed,
|
||||
|
||||
// Has received data chunks and the end of a packet. Delayed ack timer is
|
||||
// running and a SACK will be sent on expiry, or if DATA is sent, or after
|
||||
// next packet with data.
|
||||
kDelayed,
|
||||
|
||||
// Send a SACK immediately after handling this packet.
|
||||
kImmediate,
|
||||
};
|
||||
|
||||
// Represents ranges of TSNs that have been received that are not directly
|
||||
// following the last cumulative acked TSN. This information is returned to
|
||||
// the sender in the "gap ack blocks" in the SACK chunk. The blocks are always
|
||||
// non-overlapping and non-adjacent.
|
||||
class AdditionalTsnBlocks {
|
||||
public:
|
||||
// Represents an inclusive range of received TSNs, i.e. [first, last].
|
||||
struct TsnRange {
|
||||
TsnRange(UnwrappedTSN first, UnwrappedTSN last)
|
||||
: first(first), last(last) {}
|
||||
UnwrappedTSN first;
|
||||
UnwrappedTSN last;
|
||||
};
|
||||
|
||||
// Adds a TSN to the set. This will try to expand any existing block and
|
||||
// might merge blocks to ensure that all blocks are non-adjacent. If a
|
||||
// current block can't be expanded, a new block is created.
|
||||
//
|
||||
// The return value indicates if `tsn` was added. If false is returned, the
|
||||
// `tsn` was already represented in one of the blocks.
|
||||
bool Add(UnwrappedTSN tsn);
|
||||
|
||||
// Erases all TSNs up to, and including `tsn`. This will remove all blocks
|
||||
// that are completely below `tsn` and may truncate a block where `tsn` is
|
||||
// within that block. In that case, the frontmost block's start TSN will be
|
||||
// the next following tsn after `tsn`.
|
||||
void EraseTo(UnwrappedTSN tsn);
|
||||
|
||||
// Removes the first block. Must not be called on an empty set.
|
||||
void PopFront();
|
||||
|
||||
const std::vector<TsnRange>& blocks() const { return blocks_; }
|
||||
|
||||
bool empty() const { return blocks_.empty(); }
|
||||
|
||||
const TsnRange& front() const { return blocks_.front(); }
|
||||
|
||||
private:
|
||||
// A sorted vector of non-overlapping and non-adjacent blocks.
|
||||
std::vector<TsnRange> blocks_;
|
||||
};
|
||||
|
||||
std::vector<SackChunk::GapAckBlock> CreateGapAckBlocks() const;
|
||||
void UpdateAckState(AckState new_state, absl::string_view reason);
|
||||
static absl::string_view ToString(AckState ack_state);
|
||||
|
||||
const absl::string_view log_prefix_;
|
||||
// If a packet has ever been seen.
|
||||
bool seen_packet_;
|
||||
Timer& delayed_ack_timer_;
|
||||
AckState ack_state_ = AckState::kIdle;
|
||||
UnwrappedTSN::Unwrapper tsn_unwrapper_;
|
||||
|
||||
// All TSNs up until (and including) this value have been seen.
|
||||
UnwrappedTSN last_cumulative_acked_tsn_;
|
||||
// Received TSNs that are not directly following `last_cumulative_acked_tsn_`.
|
||||
AdditionalTsnBlocks additional_tsn_blocks_;
|
||||
std::set<TSN> duplicate_tsns_;
|
||||
};
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_RX_DATA_TRACKER_H_
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
/*
|
||||
* 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/interleaved_reassembly_streams.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/public/types.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
InterleavedReassemblyStreams::InterleavedReassemblyStreams(
|
||||
absl::string_view log_prefix,
|
||||
OnAssembledMessage on_assembled_message)
|
||||
: log_prefix_(log_prefix), on_assembled_message_(on_assembled_message) {}
|
||||
|
||||
size_t InterleavedReassemblyStreams::Stream::TryToAssembleMessage(
|
||||
UnwrappedMID mid) {
|
||||
std::map<UnwrappedMID, ChunkMap>::const_iterator it =
|
||||
chunks_by_mid_.find(mid);
|
||||
if (it == chunks_by_mid_.end()) {
|
||||
RTC_DLOG(LS_VERBOSE) << parent_.log_prefix_ << "TryToAssembleMessage "
|
||||
<< *mid.Wrap() << " - no chunks";
|
||||
return 0;
|
||||
}
|
||||
const ChunkMap& chunks = it->second;
|
||||
if (!chunks.begin()->second.second.is_beginning ||
|
||||
!chunks.rbegin()->second.second.is_end) {
|
||||
RTC_DLOG(LS_VERBOSE) << parent_.log_prefix_ << "TryToAssembleMessage "
|
||||
<< *mid.Wrap() << "- missing beginning or end";
|
||||
return 0;
|
||||
}
|
||||
int64_t fsn_diff = *chunks.rbegin()->first - *chunks.begin()->first;
|
||||
if (fsn_diff != (static_cast<int64_t>(chunks.size()) - 1)) {
|
||||
RTC_DLOG(LS_VERBOSE) << parent_.log_prefix_ << "TryToAssembleMessage "
|
||||
<< *mid.Wrap() << "- not all chunks exist (have "
|
||||
<< chunks.size() << ", expect " << (fsn_diff + 1)
|
||||
<< ")";
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t removed_bytes = AssembleMessage(chunks);
|
||||
RTC_DLOG(LS_VERBOSE) << parent_.log_prefix_ << "TryToAssembleMessage "
|
||||
<< *mid.Wrap() << " - succeeded and removed "
|
||||
<< removed_bytes;
|
||||
|
||||
chunks_by_mid_.erase(mid);
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
size_t InterleavedReassemblyStreams::Stream::AssembleMessage(
|
||||
const ChunkMap& tsn_chunks) {
|
||||
size_t count = tsn_chunks.size();
|
||||
if (count == 1) {
|
||||
// Fast path - zero-copy
|
||||
const Data& data = tsn_chunks.begin()->second.second;
|
||||
size_t payload_size = data.size();
|
||||
UnwrappedTSN tsns[1] = {tsn_chunks.begin()->second.first};
|
||||
DcSctpMessage message(data.stream_id, data.ppid, std::move(data.payload));
|
||||
parent_.on_assembled_message_(tsns, std::move(message));
|
||||
return payload_size;
|
||||
}
|
||||
|
||||
// Slow path - will need to concatenate the payload.
|
||||
std::vector<UnwrappedTSN> tsns;
|
||||
tsns.reserve(count);
|
||||
|
||||
std::vector<uint8_t> payload;
|
||||
size_t payload_size = absl::c_accumulate(
|
||||
tsn_chunks, 0,
|
||||
[](size_t v, const auto& p) { return v + p.second.second.size(); });
|
||||
payload.reserve(payload_size);
|
||||
|
||||
for (auto& item : tsn_chunks) {
|
||||
const UnwrappedTSN tsn = item.second.first;
|
||||
const Data& data = item.second.second;
|
||||
tsns.push_back(tsn);
|
||||
payload.insert(payload.end(), data.payload.begin(), data.payload.end());
|
||||
}
|
||||
|
||||
const Data& data = tsn_chunks.begin()->second.second;
|
||||
|
||||
DcSctpMessage message(data.stream_id, data.ppid, std::move(payload));
|
||||
parent_.on_assembled_message_(tsns, std::move(message));
|
||||
return payload_size;
|
||||
}
|
||||
|
||||
size_t InterleavedReassemblyStreams::Stream::EraseTo(MID mid) {
|
||||
UnwrappedMID unwrapped_mid = mid_unwrapper_.Unwrap(mid);
|
||||
|
||||
size_t removed_bytes = 0;
|
||||
auto it = chunks_by_mid_.begin();
|
||||
while (it != chunks_by_mid_.end() && it->first <= unwrapped_mid) {
|
||||
removed_bytes += absl::c_accumulate(
|
||||
it->second, 0,
|
||||
[](size_t r2, const auto& q) { return r2 + q.second.second.size(); });
|
||||
it = chunks_by_mid_.erase(it);
|
||||
}
|
||||
|
||||
if (!stream_id_.unordered) {
|
||||
// For ordered streams, erasing a message might suddenly unblock that queue
|
||||
// and allow it to deliver any following received messages.
|
||||
if (unwrapped_mid >= next_mid_) {
|
||||
next_mid_ = unwrapped_mid.next_value();
|
||||
}
|
||||
|
||||
removed_bytes += TryToAssembleMessages();
|
||||
}
|
||||
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
int InterleavedReassemblyStreams::Stream::Add(UnwrappedTSN tsn, Data data) {
|
||||
RTC_DCHECK_EQ(*data.is_unordered, *stream_id_.unordered);
|
||||
RTC_DCHECK_EQ(*data.stream_id, *stream_id_.stream_id);
|
||||
int queued_bytes = data.size();
|
||||
UnwrappedMID mid = mid_unwrapper_.Unwrap(data.mid);
|
||||
FSN fsn = data.fsn;
|
||||
auto [unused, inserted] =
|
||||
chunks_by_mid_[mid].emplace(fsn, std::make_pair(tsn, std::move(data)));
|
||||
if (!inserted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (stream_id_.unordered) {
|
||||
queued_bytes -= TryToAssembleMessage(mid);
|
||||
} else {
|
||||
if (mid == next_mid_) {
|
||||
queued_bytes -= TryToAssembleMessages();
|
||||
}
|
||||
}
|
||||
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
size_t InterleavedReassemblyStreams::Stream::TryToAssembleMessages() {
|
||||
size_t removed_bytes = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t removed_bytes_this_iter = TryToAssembleMessage(next_mid_);
|
||||
if (removed_bytes_this_iter == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
removed_bytes += removed_bytes_this_iter;
|
||||
next_mid_.Increment();
|
||||
}
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
void InterleavedReassemblyStreams::Stream::AddHandoverState(
|
||||
DcSctpSocketHandoverState& state) const {
|
||||
if (stream_id_.unordered) {
|
||||
DcSctpSocketHandoverState::UnorderedStream state_stream;
|
||||
state_stream.id = stream_id_.stream_id.value();
|
||||
state.rx.unordered_streams.push_back(std::move(state_stream));
|
||||
} else {
|
||||
DcSctpSocketHandoverState::OrderedStream state_stream;
|
||||
state_stream.id = stream_id_.stream_id.value();
|
||||
state_stream.next_ssn = next_mid_.Wrap().value();
|
||||
state.rx.ordered_streams.push_back(std::move(state_stream));
|
||||
}
|
||||
}
|
||||
|
||||
InterleavedReassemblyStreams::Stream&
|
||||
InterleavedReassemblyStreams::GetOrCreateStream(const FullStreamId& stream_id) {
|
||||
auto it = streams_.find(stream_id);
|
||||
if (it == streams_.end()) {
|
||||
it =
|
||||
streams_
|
||||
.emplace(std::piecewise_construct, std::forward_as_tuple(stream_id),
|
||||
std::forward_as_tuple(stream_id, this))
|
||||
.first;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
int InterleavedReassemblyStreams::Add(UnwrappedTSN tsn, Data data) {
|
||||
return GetOrCreateStream(FullStreamId(data.is_unordered, data.stream_id))
|
||||
.Add(tsn, std::move(data));
|
||||
}
|
||||
|
||||
size_t InterleavedReassemblyStreams::HandleForwardTsn(
|
||||
UnwrappedTSN new_cumulative_ack_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams) {
|
||||
size_t removed_bytes = 0;
|
||||
for (const auto& skipped : skipped_streams) {
|
||||
removed_bytes +=
|
||||
GetOrCreateStream(FullStreamId(skipped.unordered, skipped.stream_id))
|
||||
.EraseTo(skipped.mid);
|
||||
}
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
void InterleavedReassemblyStreams::ResetStreams(
|
||||
rtc::ArrayView<const StreamID> stream_ids) {
|
||||
if (stream_ids.empty()) {
|
||||
for (auto& entry : streams_) {
|
||||
entry.second.Reset();
|
||||
}
|
||||
} else {
|
||||
for (StreamID stream_id : stream_ids) {
|
||||
GetOrCreateStream(FullStreamId(IsUnordered(true), stream_id)).Reset();
|
||||
GetOrCreateStream(FullStreamId(IsUnordered(false), stream_id)).Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandoverReadinessStatus InterleavedReassemblyStreams::GetHandoverReadiness()
|
||||
const {
|
||||
HandoverReadinessStatus status;
|
||||
for (const auto& [stream_id, stream] : streams_) {
|
||||
if (stream.has_unassembled_chunks()) {
|
||||
status.Add(
|
||||
stream_id.unordered
|
||||
? HandoverUnreadinessReason::kUnorderedStreamHasUnassembledChunks
|
||||
: HandoverUnreadinessReason::kOrderedStreamHasUnassembledChunks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void InterleavedReassemblyStreams::AddHandoverState(
|
||||
DcSctpSocketHandoverState& state) {
|
||||
for (const auto& [unused, stream] : streams_) {
|
||||
stream.AddHandoverState(state);
|
||||
}
|
||||
}
|
||||
|
||||
void InterleavedReassemblyStreams::RestoreFromState(
|
||||
const DcSctpSocketHandoverState& state) {
|
||||
// Validate that the component is in pristine state.
|
||||
RTC_DCHECK(streams_.empty());
|
||||
|
||||
for (const DcSctpSocketHandoverState::OrderedStream& state :
|
||||
state.rx.ordered_streams) {
|
||||
FullStreamId stream_id(IsUnordered(false), StreamID(state.id));
|
||||
streams_.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(stream_id),
|
||||
std::forward_as_tuple(stream_id, this, MID(state.next_ssn)));
|
||||
}
|
||||
for (const DcSctpSocketHandoverState::UnorderedStream& state :
|
||||
state.rx.unordered_streams) {
|
||||
FullStreamId stream_id(IsUnordered(true), StreamID(state.id));
|
||||
streams_.emplace(std::piecewise_construct, std::forward_as_tuple(stream_id),
|
||||
std::forward_as_tuple(stream_id, this));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dcsctp
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NET_DCSCTP_RX_INTERLEAVED_REASSEMBLY_STREAMS_H_
|
||||
#define NET_DCSCTP_RX_INTERLEAVED_REASSEMBLY_STREAMS_H_
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/rx/reassembly_streams.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
// Handles reassembly of incoming data when interleaved message sending is
|
||||
// enabled on the association, i.e. when RFC8260 is in use.
|
||||
class InterleavedReassemblyStreams : public ReassemblyStreams {
|
||||
public:
|
||||
InterleavedReassemblyStreams(absl::string_view log_prefix,
|
||||
OnAssembledMessage on_assembled_message);
|
||||
|
||||
int Add(UnwrappedTSN tsn, Data data) override;
|
||||
|
||||
size_t HandleForwardTsn(
|
||||
UnwrappedTSN new_cumulative_ack_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams)
|
||||
override;
|
||||
|
||||
void ResetStreams(rtc::ArrayView<const StreamID> stream_ids) override;
|
||||
|
||||
HandoverReadinessStatus GetHandoverReadiness() const override;
|
||||
void AddHandoverState(DcSctpSocketHandoverState& state) override;
|
||||
void RestoreFromState(const DcSctpSocketHandoverState& state) override;
|
||||
|
||||
private:
|
||||
struct FullStreamId {
|
||||
const IsUnordered unordered;
|
||||
const StreamID stream_id;
|
||||
|
||||
FullStreamId(IsUnordered unordered, StreamID stream_id)
|
||||
: unordered(unordered), stream_id(stream_id) {}
|
||||
|
||||
friend bool operator<(FullStreamId a, FullStreamId b) {
|
||||
return a.unordered < b.unordered ||
|
||||
(!(a.unordered < b.unordered) && (a.stream_id < b.stream_id));
|
||||
}
|
||||
};
|
||||
|
||||
class Stream {
|
||||
public:
|
||||
Stream(FullStreamId stream_id,
|
||||
InterleavedReassemblyStreams* parent,
|
||||
MID next_mid = MID(0))
|
||||
: stream_id_(stream_id),
|
||||
parent_(*parent),
|
||||
next_mid_(mid_unwrapper_.Unwrap(next_mid)) {}
|
||||
int Add(UnwrappedTSN tsn, Data data);
|
||||
size_t EraseTo(MID mid);
|
||||
void Reset() {
|
||||
mid_unwrapper_.Reset();
|
||||
next_mid_ = mid_unwrapper_.Unwrap(MID(0));
|
||||
}
|
||||
bool has_unassembled_chunks() const { return !chunks_by_mid_.empty(); }
|
||||
void AddHandoverState(DcSctpSocketHandoverState& state) const;
|
||||
|
||||
private:
|
||||
using ChunkMap = std::map<FSN, std::pair<UnwrappedTSN, Data>>;
|
||||
|
||||
// Try to assemble one message identified by `mid`.
|
||||
// Returns the number of bytes assembled if a message was assembled.
|
||||
size_t TryToAssembleMessage(UnwrappedMID mid);
|
||||
size_t AssembleMessage(const ChunkMap& tsn_chunks);
|
||||
// Try to assemble one or several messages in order from the stream.
|
||||
// Returns the number of bytes assembled if one or more messages were
|
||||
// assembled.
|
||||
size_t TryToAssembleMessages();
|
||||
|
||||
const FullStreamId stream_id_;
|
||||
InterleavedReassemblyStreams& parent_;
|
||||
std::map<UnwrappedMID, ChunkMap> chunks_by_mid_;
|
||||
UnwrappedMID::Unwrapper mid_unwrapper_;
|
||||
UnwrappedMID next_mid_;
|
||||
};
|
||||
|
||||
Stream& GetOrCreateStream(const FullStreamId& stream_id);
|
||||
|
||||
const absl::string_view log_prefix_;
|
||||
|
||||
// Callback for when a message has been assembled.
|
||||
const OnAssembledMessage on_assembled_message_;
|
||||
|
||||
// All unordered and ordered streams, managing not-yet-assembled data.
|
||||
std::map<FullStreamId, Stream> streams_;
|
||||
};
|
||||
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_RX_INTERLEAVED_REASSEMBLY_STREAMS_H_
|
||||
238
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/reassembly_queue.cc
Normal file
238
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/reassembly_queue.cc
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
* 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 <cstdint>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
|
||||
#include "net/dcsctp/packet/parameter/reconfiguration_response_parameter.h"
|
||||
#include "net/dcsctp/public/dcsctp_message.h"
|
||||
#include "net/dcsctp/public/types.h"
|
||||
#include "net/dcsctp/rx/interleaved_reassembly_streams.h"
|
||||
#include "net/dcsctp/rx/reassembly_streams.h"
|
||||
#include "net/dcsctp/rx/traditional_reassembly_streams.h"
|
||||
#include "rtc_base/logging.h"
|
||||
#include "rtc_base/strings/str_join.h"
|
||||
|
||||
namespace dcsctp {
|
||||
namespace {
|
||||
std::unique_ptr<ReassemblyStreams> CreateStreams(
|
||||
absl::string_view log_prefix,
|
||||
ReassemblyStreams::OnAssembledMessage on_assembled_message,
|
||||
bool use_message_interleaving) {
|
||||
if (use_message_interleaving) {
|
||||
return std::make_unique<InterleavedReassemblyStreams>(
|
||||
log_prefix, std::move(on_assembled_message));
|
||||
}
|
||||
return std::make_unique<TraditionalReassemblyStreams>(
|
||||
log_prefix, std::move(on_assembled_message));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
ReassemblyQueue::ReassemblyQueue(absl::string_view log_prefix,
|
||||
TSN peer_initial_tsn,
|
||||
size_t max_size_bytes,
|
||||
bool use_message_interleaving)
|
||||
: log_prefix_(log_prefix),
|
||||
max_size_bytes_(max_size_bytes),
|
||||
watermark_bytes_(max_size_bytes * kHighWatermarkLimit),
|
||||
last_completed_reset_req_seq_nbr_(ReconfigRequestSN(0)),
|
||||
streams_(CreateStreams(
|
||||
log_prefix_,
|
||||
[this](rtc::ArrayView<const UnwrappedTSN> tsns,
|
||||
DcSctpMessage message) {
|
||||
AddReassembledMessage(tsns, std::move(message));
|
||||
},
|
||||
use_message_interleaving)) {}
|
||||
|
||||
void ReassemblyQueue::Add(TSN tsn, Data data) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "added tsn=" << *tsn
|
||||
<< ", stream=" << *data.stream_id << ":" << *data.mid
|
||||
<< ":" << *data.fsn << ", type="
|
||||
<< (data.is_beginning && data.is_end ? "complete"
|
||||
: data.is_beginning ? "first"
|
||||
: data.is_end ? "last"
|
||||
: "middle");
|
||||
|
||||
UnwrappedTSN unwrapped_tsn = tsn_unwrapper_.Unwrap(tsn);
|
||||
|
||||
// If a stream reset has been received with a "sender's last assigned tsn" in
|
||||
// the future, the socket is in "deferred reset processing" mode and must
|
||||
// buffer chunks until it's exited.
|
||||
if (deferred_reset_streams_.has_value() &&
|
||||
unwrapped_tsn > deferred_reset_streams_->sender_last_assigned_tsn &&
|
||||
deferred_reset_streams_->streams.contains(data.stream_id)) {
|
||||
RTC_DLOG(LS_VERBOSE)
|
||||
<< log_prefix_ << "Deferring chunk with tsn=" << *tsn
|
||||
<< ", sid=" << *data.stream_id << " until tsn="
|
||||
<< *deferred_reset_streams_->sender_last_assigned_tsn.Wrap();
|
||||
// https://tools.ietf.org/html/rfc6525#section-5.2.2
|
||||
// "In this mode, any data arriving with a TSN larger than the
|
||||
// Sender's Last Assigned TSN for the affected stream(s) MUST be queued
|
||||
// locally and held until the cumulative acknowledgment point reaches the
|
||||
// Sender's Last Assigned TSN."
|
||||
queued_bytes_ += data.size();
|
||||
deferred_reset_streams_->deferred_actions.push_back(
|
||||
[this, tsn, data = std::move(data)]() mutable {
|
||||
queued_bytes_ -= data.size();
|
||||
Add(tsn, std::move(data));
|
||||
});
|
||||
} else {
|
||||
queued_bytes_ += streams_->Add(unwrapped_tsn, std::move(data));
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc4960#section-6.9
|
||||
// "Note: If the data receiver runs out of buffer space while still
|
||||
// waiting for more fragments to complete the reassembly of the message, it
|
||||
// should dispatch part of its inbound message through a partial delivery
|
||||
// API (see Section 10), freeing some of its receive buffer space so that
|
||||
// the rest of the message may be received."
|
||||
|
||||
// TODO(boivie): Support EOR flag and partial delivery?
|
||||
RTC_DCHECK(IsConsistent());
|
||||
}
|
||||
|
||||
void ReassemblyQueue::ResetStreamsAndLeaveDeferredReset(
|
||||
rtc::ArrayView<const StreamID> stream_ids) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "Resetting streams: ["
|
||||
<< StrJoin(stream_ids, ",",
|
||||
[](rtc::StringBuilder& sb, StreamID sid) {
|
||||
sb << *sid;
|
||||
})
|
||||
<< "]";
|
||||
|
||||
// https://tools.ietf.org/html/rfc6525#section-5.2.2
|
||||
// "... streams MUST be reset to 0 as the next expected SSN."
|
||||
streams_->ResetStreams(stream_ids);
|
||||
|
||||
if (deferred_reset_streams_.has_value()) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_
|
||||
<< "Leaving deferred reset processing, feeding back "
|
||||
<< deferred_reset_streams_->deferred_actions.size()
|
||||
<< " actions";
|
||||
// https://tools.ietf.org/html/rfc6525#section-5.2.2
|
||||
// "Any queued TSNs (queued at step E2) MUST now be released and processed
|
||||
// normally."
|
||||
auto deferred_actions =
|
||||
std::move(deferred_reset_streams_->deferred_actions);
|
||||
deferred_reset_streams_ = absl::nullopt;
|
||||
|
||||
for (auto& action : deferred_actions) {
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
RTC_DCHECK(IsConsistent());
|
||||
}
|
||||
|
||||
void ReassemblyQueue::EnterDeferredReset(
|
||||
TSN sender_last_assigned_tsn,
|
||||
rtc::ArrayView<const StreamID> streams) {
|
||||
if (!deferred_reset_streams_.has_value()) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_
|
||||
<< "Entering deferred reset; sender_last_assigned_tsn="
|
||||
<< *sender_last_assigned_tsn;
|
||||
deferred_reset_streams_ = absl::make_optional<DeferredResetStreams>(
|
||||
tsn_unwrapper_.Unwrap(sender_last_assigned_tsn),
|
||||
webrtc::flat_set<StreamID>(streams.begin(), streams.end()));
|
||||
}
|
||||
RTC_DCHECK(IsConsistent());
|
||||
}
|
||||
|
||||
std::vector<DcSctpMessage> ReassemblyQueue::FlushMessages() {
|
||||
std::vector<DcSctpMessage> ret;
|
||||
reassembled_messages_.swap(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ReassemblyQueue::AddReassembledMessage(
|
||||
rtc::ArrayView<const UnwrappedTSN> tsns,
|
||||
DcSctpMessage message) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "Assembled message from TSN=["
|
||||
<< StrJoin(tsns, ",",
|
||||
[](rtc::StringBuilder& sb, UnwrappedTSN tsn) {
|
||||
sb << *tsn.Wrap();
|
||||
})
|
||||
<< "], message; stream_id=" << *message.stream_id()
|
||||
<< ", ppid=" << *message.ppid()
|
||||
<< ", payload=" << message.payload().size() << " bytes";
|
||||
|
||||
reassembled_messages_.emplace_back(std::move(message));
|
||||
}
|
||||
|
||||
void ReassemblyQueue::HandleForwardTsn(
|
||||
TSN new_cumulative_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams) {
|
||||
UnwrappedTSN tsn = tsn_unwrapper_.Unwrap(new_cumulative_tsn);
|
||||
|
||||
if (deferred_reset_streams_.has_value() &&
|
||||
tsn > deferred_reset_streams_->sender_last_assigned_tsn) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "ForwardTSN to " << *tsn.Wrap()
|
||||
<< "- deferring.";
|
||||
deferred_reset_streams_->deferred_actions.emplace_back(
|
||||
[this, new_cumulative_tsn,
|
||||
streams = std::vector<AnyForwardTsnChunk::SkippedStream>(
|
||||
skipped_streams.begin(), skipped_streams.end())] {
|
||||
HandleForwardTsn(new_cumulative_tsn, streams);
|
||||
});
|
||||
RTC_DCHECK(IsConsistent());
|
||||
return;
|
||||
}
|
||||
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "ForwardTSN to " << *tsn.Wrap()
|
||||
<< " - performing.";
|
||||
queued_bytes_ -= streams_->HandleForwardTsn(tsn, skipped_streams);
|
||||
RTC_DCHECK(IsConsistent());
|
||||
}
|
||||
|
||||
bool ReassemblyQueue::IsConsistent() const {
|
||||
// Allow queued_bytes_ to be larger than max_size_bytes, as it's not actively
|
||||
// enforced in this class. But in case it wraps around (becomes negative, but
|
||||
// as it's unsigned, that would wrap to very big), this would trigger.
|
||||
return (queued_bytes_ <= 2 * max_size_bytes_);
|
||||
}
|
||||
|
||||
HandoverReadinessStatus ReassemblyQueue::GetHandoverReadiness() const {
|
||||
HandoverReadinessStatus status = streams_->GetHandoverReadiness();
|
||||
if (deferred_reset_streams_.has_value()) {
|
||||
status.Add(HandoverUnreadinessReason::kStreamResetDeferred);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void ReassemblyQueue::AddHandoverState(DcSctpSocketHandoverState& state) {
|
||||
state.rx.last_completed_deferred_reset_req_sn =
|
||||
last_completed_reset_req_seq_nbr_.value();
|
||||
streams_->AddHandoverState(state);
|
||||
}
|
||||
|
||||
void ReassemblyQueue::RestoreFromState(const DcSctpSocketHandoverState& state) {
|
||||
// Validate that the component is in pristine state.
|
||||
RTC_DCHECK(last_completed_reset_req_seq_nbr_ == ReconfigRequestSN(0));
|
||||
|
||||
last_completed_reset_req_seq_nbr_ =
|
||||
ReconfigRequestSN(state.rx.last_completed_deferred_reset_req_sn);
|
||||
streams_->RestoreFromState(state);
|
||||
}
|
||||
} // namespace dcsctp
|
||||
170
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/reassembly_queue.h
Normal file
170
TMessagesProj/jni/voip/webrtc/net/dcsctp/rx/reassembly_queue.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NET_DCSCTP_RX_REASSEMBLY_QUEUE_H_
|
||||
#define NET_DCSCTP_RX_REASSEMBLY_QUEUE_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/functional/any_invocable.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/internal_types.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
|
||||
#include "net/dcsctp/packet/parameter/reconfiguration_response_parameter.h"
|
||||
#include "net/dcsctp/public/dcsctp_handover_state.h"
|
||||
#include "net/dcsctp/public/dcsctp_message.h"
|
||||
#include "net/dcsctp/rx/reassembly_streams.h"
|
||||
#include "rtc_base/containers/flat_set.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
// Contains the received DATA chunks that haven't yet been reassembled, and
|
||||
// reassembles chunks when possible.
|
||||
//
|
||||
// The actual assembly is handled by an implementation of the
|
||||
// `ReassemblyStreams` interface.
|
||||
//
|
||||
// Except for reassembling fragmented messages, this class will also handle two
|
||||
// less common operations; To handle the receiver-side of partial reliability
|
||||
// (limited number of retransmissions or limited message lifetime) as well as
|
||||
// stream resetting, which is used when a sender wishes to close a data channel.
|
||||
//
|
||||
// Partial reliability is handled when a FORWARD-TSN or I-FORWARD-TSN chunk is
|
||||
// received, and it will simply delete any chunks matching the parameters in
|
||||
// that chunk. This is mainly implemented in ReassemblyStreams.
|
||||
//
|
||||
// Resetting streams is handled when a RECONFIG chunks is received, with an
|
||||
// "Outgoing SSN Reset Request" parameter. That parameter will contain a list of
|
||||
// streams to reset, and a `sender_last_assigned_tsn`. If this TSN is not yet
|
||||
// seen, the stream cannot be directly reset, and this class will respond that
|
||||
// the reset is "deferred". But if this TSN provided is known, the stream can be
|
||||
// immediately be reset.
|
||||
//
|
||||
// The ReassemblyQueue has a maximum size, as it would otherwise be an DoS
|
||||
// attack vector where a peer could consume all memory of the other peer by
|
||||
// sending a lot of ordered chunks, but carefully withholding an early one. It
|
||||
// also has a watermark limit, which the caller can query is the number of bytes
|
||||
// is above that limit. This is used by the caller to be selective in what to
|
||||
// add to the reassembly queue, so that it's not exhausted. The caller is
|
||||
// expected to call `is_full` prior to adding data to the queue and to act
|
||||
// accordingly if the queue is full.
|
||||
class ReassemblyQueue {
|
||||
public:
|
||||
// When the queue is filled over this fraction (of its maximum size), the
|
||||
// socket should restrict incoming data to avoid filling up the queue.
|
||||
static constexpr float kHighWatermarkLimit = 0.9;
|
||||
|
||||
ReassemblyQueue(absl::string_view log_prefix,
|
||||
TSN peer_initial_tsn,
|
||||
size_t max_size_bytes,
|
||||
bool use_message_interleaving = false);
|
||||
|
||||
// Adds a data chunk to the queue, with a `tsn` and other parameters in
|
||||
// `data`.
|
||||
void Add(TSN tsn, Data data);
|
||||
|
||||
// Indicates if the reassembly queue has any reassembled messages that can be
|
||||
// retrieved by calling `FlushMessages`.
|
||||
bool HasMessages() const { return !reassembled_messages_.empty(); }
|
||||
|
||||
// Returns any reassembled messages.
|
||||
std::vector<DcSctpMessage> FlushMessages();
|
||||
|
||||
// Handle a ForwardTSN chunk, when the sender has indicated that the received
|
||||
// (this class) should forget about some chunks. This is used to implement
|
||||
// partial reliability.
|
||||
void HandleForwardTsn(
|
||||
TSN new_cumulative_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams);
|
||||
|
||||
// Resets the provided streams and leaves deferred reset processing, if
|
||||
// enabled.
|
||||
void ResetStreamsAndLeaveDeferredReset(
|
||||
rtc::ArrayView<const StreamID> stream_ids);
|
||||
|
||||
// Enters deferred reset processing.
|
||||
void EnterDeferredReset(TSN sender_last_assigned_tsn,
|
||||
rtc::ArrayView<const StreamID> streams);
|
||||
|
||||
// The number of payload bytes that have been queued. Note that the actual
|
||||
// memory usage is higher due to additional overhead of tracking received
|
||||
// data.
|
||||
size_t queued_bytes() const { return queued_bytes_; }
|
||||
|
||||
// The remaining bytes until the queue has reached the watermark limit.
|
||||
size_t remaining_bytes() const { return watermark_bytes_ - queued_bytes_; }
|
||||
|
||||
// Indicates if the queue is full. Data should not be added to the queue when
|
||||
// it's full.
|
||||
bool is_full() const { return queued_bytes_ >= max_size_bytes_; }
|
||||
|
||||
// Indicates if the queue is above the watermark limit, which is a certain
|
||||
// percentage of its size.
|
||||
bool is_above_watermark() const { return queued_bytes_ >= watermark_bytes_; }
|
||||
|
||||
// Returns the watermark limit, in bytes.
|
||||
size_t watermark_bytes() const { return watermark_bytes_; }
|
||||
|
||||
HandoverReadinessStatus GetHandoverReadiness() const;
|
||||
|
||||
void AddHandoverState(DcSctpSocketHandoverState& state);
|
||||
void RestoreFromState(const DcSctpSocketHandoverState& state);
|
||||
|
||||
private:
|
||||
struct DeferredResetStreams {
|
||||
DeferredResetStreams(UnwrappedTSN sender_last_assigned_tsn,
|
||||
webrtc::flat_set<StreamID> streams)
|
||||
: sender_last_assigned_tsn(sender_last_assigned_tsn),
|
||||
streams(std::move(streams)) {}
|
||||
|
||||
UnwrappedTSN sender_last_assigned_tsn;
|
||||
webrtc::flat_set<StreamID> streams;
|
||||
std::vector<absl::AnyInvocable<void(void)>> deferred_actions;
|
||||
};
|
||||
|
||||
bool IsConsistent() const;
|
||||
void AddReassembledMessage(rtc::ArrayView<const UnwrappedTSN> tsns,
|
||||
DcSctpMessage message);
|
||||
|
||||
const absl::string_view log_prefix_;
|
||||
const size_t max_size_bytes_;
|
||||
const size_t watermark_bytes_;
|
||||
UnwrappedTSN::Unwrapper tsn_unwrapper_;
|
||||
|
||||
// Messages that have been reassembled, and will be returned by
|
||||
// `FlushMessages`.
|
||||
std::vector<DcSctpMessage> reassembled_messages_;
|
||||
|
||||
// If present, "deferred reset processing" mode is active.
|
||||
absl::optional<DeferredResetStreams> deferred_reset_streams_;
|
||||
|
||||
// Contains the last request sequence number of the
|
||||
// OutgoingSSNResetRequestParameter that was performed.
|
||||
ReconfigRequestSN last_completed_reset_req_seq_nbr_;
|
||||
|
||||
// The number of "payload bytes" that are in this queue, in total.
|
||||
size_t queued_bytes_ = 0;
|
||||
|
||||
// The actual implementation of ReassemblyStreams.
|
||||
std::unique_ptr<ReassemblyStreams> streams_;
|
||||
};
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_RX_REASSEMBLY_QUEUE_H_
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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_streams.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
ReassembledMessage AssembleMessage(std::map<UnwrappedTSN, Data>::iterator start,
|
||||
std::map<UnwrappedTSN, Data>::iterator end) {
|
||||
size_t count = std::distance(start, end);
|
||||
|
||||
if (count == 1) {
|
||||
// Fast path - zero-copy
|
||||
Data& data = start->second;
|
||||
|
||||
return ReassembledMessage{
|
||||
.tsns = {start->first},
|
||||
.message = DcSctpMessage(data.stream_id, data.ppid,
|
||||
std::move(start->second.payload)),
|
||||
};
|
||||
}
|
||||
|
||||
// Slow path - will need to concatenate the payload.
|
||||
std::vector<UnwrappedTSN> tsns;
|
||||
std::vector<uint8_t> payload;
|
||||
|
||||
size_t payload_size = std::accumulate(
|
||||
start, end, 0,
|
||||
[](size_t v, const auto& p) { return v + p.second.size(); });
|
||||
|
||||
tsns.reserve(count);
|
||||
payload.reserve(payload_size);
|
||||
for (auto it = start; it != end; ++it) {
|
||||
Data& data = it->second;
|
||||
tsns.push_back(it->first);
|
||||
payload.insert(payload.end(), data.payload.begin(), data.payload.end());
|
||||
}
|
||||
|
||||
return ReassembledMessage{
|
||||
.tsns = std::move(tsns),
|
||||
.message = DcSctpMessage(start->second.stream_id, start->second.ppid,
|
||||
std::move(payload)),
|
||||
};
|
||||
}
|
||||
} // namespace dcsctp
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NET_DCSCTP_RX_REASSEMBLY_STREAMS_H_
|
||||
#define NET_DCSCTP_RX_REASSEMBLY_STREAMS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/public/dcsctp_handover_state.h"
|
||||
#include "net/dcsctp/public/dcsctp_message.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
// Implementations of this interface will be called when data is received, when
|
||||
// data should be skipped/forgotten or when sequence number should be reset.
|
||||
//
|
||||
// As a result of these operations - mainly when data is received - the
|
||||
// implementations of this interface should notify when a message has been
|
||||
// assembled, by calling the provided callback of type `OnAssembledMessage`. How
|
||||
// it assembles messages will depend on e.g. if a message was sent on an ordered
|
||||
// or unordered stream.
|
||||
//
|
||||
// Implementations will - for each operation - indicate how much additional
|
||||
// memory that has been used as a result of performing the operation. This is
|
||||
// used to limit the maximum amount of memory used, to prevent out-of-memory
|
||||
// situations.
|
||||
class ReassemblyStreams {
|
||||
public:
|
||||
// This callback will be provided as an argument to the constructor of the
|
||||
// concrete class implementing this interface and should be called when a
|
||||
// message has been assembled as well as indicating from which TSNs this
|
||||
// message was assembled from.
|
||||
using OnAssembledMessage =
|
||||
std::function<void(rtc::ArrayView<const UnwrappedTSN> tsns,
|
||||
DcSctpMessage message)>;
|
||||
|
||||
virtual ~ReassemblyStreams() = default;
|
||||
|
||||
// Adds a data chunk to a stream as identified in `data`.
|
||||
// If it was the last remaining chunk in a message, reassemble one (or
|
||||
// several, in case of ordered chunks) messages.
|
||||
//
|
||||
// Returns the additional number of bytes added to the queue as a result of
|
||||
// performing this operation. If this addition resulted in messages being
|
||||
// assembled and delivered, this may be negative.
|
||||
virtual int Add(UnwrappedTSN tsn, Data data) = 0;
|
||||
|
||||
// Called for incoming FORWARD-TSN/I-FORWARD-TSN chunks - when the sender
|
||||
// wishes the received to skip/forget about data up until the provided TSN.
|
||||
// This is used to implement partial reliability, such as limiting the number
|
||||
// of retransmissions or the an expiration duration. As a result of skipping
|
||||
// data, this may result in the implementation being able to assemble messages
|
||||
// in ordered streams.
|
||||
//
|
||||
// Returns the number of bytes removed from the queue as a result of
|
||||
// this operation.
|
||||
virtual size_t HandleForwardTsn(
|
||||
UnwrappedTSN new_cumulative_ack_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream>
|
||||
skipped_streams) = 0;
|
||||
|
||||
// Called for incoming (possibly deferred) RE_CONFIG chunks asking for
|
||||
// either a few streams, or all streams (when the list is empty) to be
|
||||
// reset - to have their next SSN or Message ID to be zero.
|
||||
virtual void ResetStreams(rtc::ArrayView<const StreamID> stream_ids) = 0;
|
||||
|
||||
virtual HandoverReadinessStatus GetHandoverReadiness() const = 0;
|
||||
virtual void AddHandoverState(DcSctpSocketHandoverState& state) = 0;
|
||||
virtual void RestoreFromState(const DcSctpSocketHandoverState& state) = 0;
|
||||
};
|
||||
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_RX_REASSEMBLY_STREAMS_H_
|
||||
|
|
@ -0,0 +1,379 @@
|
|||
/*
|
||||
* 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/traditional_reassembly_streams.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
#include <numeric>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/algorithm/container.h"
|
||||
#include "absl/types/optional.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/public/dcsctp_message.h"
|
||||
#include "rtc_base/logging.h"
|
||||
|
||||
namespace dcsctp {
|
||||
namespace {
|
||||
|
||||
// Given a map (`chunks`) and an iterator to within that map (`iter`), this
|
||||
// function will return an iterator to the first chunk in that message, which
|
||||
// has the `is_beginning` flag set. If there are any gaps, or if the beginning
|
||||
// can't be found, `absl::nullopt` is returned.
|
||||
absl::optional<std::map<UnwrappedTSN, Data>::iterator> FindBeginning(
|
||||
const std::map<UnwrappedTSN, Data>& chunks,
|
||||
std::map<UnwrappedTSN, Data>::iterator iter) {
|
||||
UnwrappedTSN prev_tsn = iter->first;
|
||||
for (;;) {
|
||||
if (iter->second.is_beginning) {
|
||||
return iter;
|
||||
}
|
||||
if (iter == chunks.begin()) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
--iter;
|
||||
if (iter->first.next_value() != prev_tsn) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
prev_tsn = iter->first;
|
||||
}
|
||||
}
|
||||
|
||||
// Given a map (`chunks`) and an iterator to within that map (`iter`), this
|
||||
// function will return an iterator to the chunk after the last chunk in that
|
||||
// message, which has the `is_end` flag set. If there are any gaps, or if the
|
||||
// end can't be found, `absl::nullopt` is returned.
|
||||
absl::optional<std::map<UnwrappedTSN, Data>::iterator> FindEnd(
|
||||
std::map<UnwrappedTSN, Data>& chunks,
|
||||
std::map<UnwrappedTSN, Data>::iterator iter) {
|
||||
UnwrappedTSN prev_tsn = iter->first;
|
||||
for (;;) {
|
||||
if (iter->second.is_end) {
|
||||
return ++iter;
|
||||
}
|
||||
++iter;
|
||||
if (iter == chunks.end()) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
if (iter->first != prev_tsn.next_value()) {
|
||||
return absl::nullopt;
|
||||
}
|
||||
prev_tsn = iter->first;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TraditionalReassemblyStreams::TraditionalReassemblyStreams(
|
||||
absl::string_view log_prefix,
|
||||
OnAssembledMessage on_assembled_message)
|
||||
: log_prefix_(log_prefix),
|
||||
on_assembled_message_(std::move(on_assembled_message)) {}
|
||||
|
||||
int TraditionalReassemblyStreams::UnorderedStream::Add(UnwrappedTSN tsn,
|
||||
Data data) {
|
||||
if (data.is_beginning && data.is_end) {
|
||||
// Fastpath for already assembled chunks.
|
||||
AssembleMessage(tsn, std::move(data));
|
||||
return 0;
|
||||
}
|
||||
int queued_bytes = data.size();
|
||||
auto [it, inserted] = chunks_.emplace(tsn, std::move(data));
|
||||
if (!inserted) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
queued_bytes -= TryToAssembleMessage(it);
|
||||
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::UnorderedStream::TryToAssembleMessage(
|
||||
ChunkMap::iterator iter) {
|
||||
// TODO(boivie): This method is O(N) with the number of fragments in a
|
||||
// message, which can be inefficient for very large values of N. This could be
|
||||
// optimized by e.g. only trying to assemble a message once _any_ beginning
|
||||
// and _any_ end has been found.
|
||||
absl::optional<ChunkMap::iterator> start = FindBeginning(chunks_, iter);
|
||||
if (!start.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
absl::optional<ChunkMap::iterator> end = FindEnd(chunks_, iter);
|
||||
if (!end.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t bytes_assembled = AssembleMessage(*start, *end);
|
||||
chunks_.erase(*start, *end);
|
||||
return bytes_assembled;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(
|
||||
const ChunkMap::iterator start,
|
||||
const ChunkMap::iterator end) {
|
||||
size_t count = std::distance(start, end);
|
||||
|
||||
if (count == 1) {
|
||||
// Fast path - zero-copy
|
||||
return AssembleMessage(start->first, std::move(start->second));
|
||||
}
|
||||
|
||||
// Slow path - will need to concatenate the payload.
|
||||
std::vector<UnwrappedTSN> tsns;
|
||||
std::vector<uint8_t> payload;
|
||||
|
||||
size_t payload_size = std::accumulate(
|
||||
start, end, 0,
|
||||
[](size_t v, const auto& p) { return v + p.second.size(); });
|
||||
|
||||
tsns.reserve(count);
|
||||
payload.reserve(payload_size);
|
||||
for (auto it = start; it != end; ++it) {
|
||||
const Data& data = it->second;
|
||||
tsns.push_back(it->first);
|
||||
payload.insert(payload.end(), data.payload.begin(), data.payload.end());
|
||||
}
|
||||
|
||||
DcSctpMessage message(start->second.stream_id, start->second.ppid,
|
||||
std::move(payload));
|
||||
parent_.on_assembled_message_(tsns, std::move(message));
|
||||
|
||||
return payload_size;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::StreamBase::AssembleMessage(
|
||||
UnwrappedTSN tsn,
|
||||
Data data) {
|
||||
// Fast path - zero-copy
|
||||
size_t payload_size = data.size();
|
||||
UnwrappedTSN tsns[1] = {tsn};
|
||||
DcSctpMessage message(data.stream_id, data.ppid, std::move(data.payload));
|
||||
parent_.on_assembled_message_(tsns, std::move(message));
|
||||
return payload_size;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::UnorderedStream::EraseTo(
|
||||
UnwrappedTSN tsn) {
|
||||
auto end_iter = chunks_.upper_bound(tsn);
|
||||
size_t removed_bytes = std::accumulate(
|
||||
chunks_.begin(), end_iter, 0,
|
||||
[](size_t r, const auto& p) { return r + p.second.size(); });
|
||||
|
||||
chunks_.erase(chunks_.begin(), end_iter);
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessage() {
|
||||
if (chunks_by_ssn_.empty() || chunks_by_ssn_.begin()->first != next_ssn_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ChunkMap& chunks = chunks_by_ssn_.begin()->second;
|
||||
|
||||
if (!chunks.begin()->second.is_beginning || !chunks.rbegin()->second.is_end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t tsn_diff =
|
||||
UnwrappedTSN::Difference(chunks.rbegin()->first, chunks.begin()->first);
|
||||
if (tsn_diff != chunks.size() - 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t assembled_bytes = AssembleMessage(chunks.begin(), chunks.end());
|
||||
chunks_by_ssn_.erase(chunks_by_ssn_.begin());
|
||||
next_ssn_.Increment();
|
||||
return assembled_bytes;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessages() {
|
||||
size_t assembled_bytes = 0;
|
||||
|
||||
for (;;) {
|
||||
size_t assembled_bytes_this_iter = TryToAssembleMessage();
|
||||
if (assembled_bytes_this_iter == 0) {
|
||||
break;
|
||||
}
|
||||
assembled_bytes += assembled_bytes_this_iter;
|
||||
}
|
||||
return assembled_bytes;
|
||||
}
|
||||
|
||||
size_t
|
||||
TraditionalReassemblyStreams::OrderedStream::TryToAssembleMessagesFastpath(
|
||||
UnwrappedSSN ssn,
|
||||
UnwrappedTSN tsn,
|
||||
Data data) {
|
||||
RTC_DCHECK(ssn == next_ssn_);
|
||||
size_t assembled_bytes = 0;
|
||||
if (data.is_beginning && data.is_end) {
|
||||
assembled_bytes += AssembleMessage(tsn, std::move(data));
|
||||
next_ssn_.Increment();
|
||||
} else {
|
||||
size_t queued_bytes = data.size();
|
||||
auto [iter, inserted] = chunks_by_ssn_[ssn].emplace(tsn, std::move(data));
|
||||
if (!inserted) {
|
||||
// Not actually assembled, but deduplicated meaning queued size doesn't
|
||||
// include this message.
|
||||
return queued_bytes;
|
||||
}
|
||||
}
|
||||
return assembled_bytes + TryToAssembleMessages();
|
||||
}
|
||||
|
||||
int TraditionalReassemblyStreams::OrderedStream::Add(UnwrappedTSN tsn,
|
||||
Data data) {
|
||||
int queued_bytes = data.size();
|
||||
UnwrappedSSN ssn = ssn_unwrapper_.Unwrap(data.ssn);
|
||||
if (ssn == next_ssn_) {
|
||||
return queued_bytes -
|
||||
TryToAssembleMessagesFastpath(ssn, tsn, std::move(data));
|
||||
}
|
||||
auto [iter, inserted] = chunks_by_ssn_[ssn].emplace(tsn, std::move(data));
|
||||
if (!inserted) {
|
||||
return 0;
|
||||
}
|
||||
return queued_bytes;
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::OrderedStream::EraseTo(SSN ssn) {
|
||||
UnwrappedSSN unwrapped_ssn = ssn_unwrapper_.Unwrap(ssn);
|
||||
|
||||
auto end_iter = chunks_by_ssn_.upper_bound(unwrapped_ssn);
|
||||
size_t removed_bytes = std::accumulate(
|
||||
chunks_by_ssn_.begin(), end_iter, 0, [](size_t r1, const auto& p) {
|
||||
return r1 +
|
||||
absl::c_accumulate(p.second, 0, [](size_t r2, const auto& q) {
|
||||
return r2 + q.second.size();
|
||||
});
|
||||
});
|
||||
chunks_by_ssn_.erase(chunks_by_ssn_.begin(), end_iter);
|
||||
|
||||
if (unwrapped_ssn >= next_ssn_) {
|
||||
unwrapped_ssn.Increment();
|
||||
next_ssn_ = unwrapped_ssn;
|
||||
}
|
||||
|
||||
removed_bytes += TryToAssembleMessages();
|
||||
return removed_bytes;
|
||||
}
|
||||
|
||||
int TraditionalReassemblyStreams::Add(UnwrappedTSN tsn, Data data) {
|
||||
if (data.is_unordered) {
|
||||
auto it = unordered_streams_.try_emplace(data.stream_id, this).first;
|
||||
return it->second.Add(tsn, std::move(data));
|
||||
}
|
||||
|
||||
auto it = ordered_streams_.try_emplace(data.stream_id, this).first;
|
||||
return it->second.Add(tsn, std::move(data));
|
||||
}
|
||||
|
||||
size_t TraditionalReassemblyStreams::HandleForwardTsn(
|
||||
UnwrappedTSN new_cumulative_ack_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams) {
|
||||
size_t bytes_removed = 0;
|
||||
// The `skipped_streams` only cover ordered messages - need to
|
||||
// iterate all unordered streams manually to remove those chunks.
|
||||
for (auto& [unused, stream] : unordered_streams_) {
|
||||
bytes_removed += stream.EraseTo(new_cumulative_ack_tsn);
|
||||
}
|
||||
|
||||
for (const auto& skipped_stream : skipped_streams) {
|
||||
auto it =
|
||||
ordered_streams_.try_emplace(skipped_stream.stream_id, this).first;
|
||||
bytes_removed += it->second.EraseTo(skipped_stream.ssn);
|
||||
}
|
||||
|
||||
return bytes_removed;
|
||||
}
|
||||
|
||||
void TraditionalReassemblyStreams::ResetStreams(
|
||||
rtc::ArrayView<const StreamID> stream_ids) {
|
||||
if (stream_ids.empty()) {
|
||||
for (auto& [stream_id, stream] : ordered_streams_) {
|
||||
RTC_DLOG(LS_VERBOSE) << log_prefix_
|
||||
<< "Resetting implicit stream_id=" << *stream_id;
|
||||
stream.Reset();
|
||||
}
|
||||
} else {
|
||||
for (StreamID stream_id : stream_ids) {
|
||||
auto it = ordered_streams_.find(stream_id);
|
||||
if (it != ordered_streams_.end()) {
|
||||
RTC_DLOG(LS_VERBOSE)
|
||||
<< log_prefix_ << "Resetting explicit stream_id=" << *stream_id;
|
||||
it->second.Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HandoverReadinessStatus TraditionalReassemblyStreams::GetHandoverReadiness()
|
||||
const {
|
||||
HandoverReadinessStatus status;
|
||||
for (const auto& [unused, stream] : ordered_streams_) {
|
||||
if (stream.has_unassembled_chunks()) {
|
||||
status.Add(HandoverUnreadinessReason::kOrderedStreamHasUnassembledChunks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const auto& [unused, stream] : unordered_streams_) {
|
||||
if (stream.has_unassembled_chunks()) {
|
||||
status.Add(
|
||||
HandoverUnreadinessReason::kUnorderedStreamHasUnassembledChunks);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void TraditionalReassemblyStreams::AddHandoverState(
|
||||
DcSctpSocketHandoverState& state) {
|
||||
for (const auto& [stream_id, stream] : ordered_streams_) {
|
||||
DcSctpSocketHandoverState::OrderedStream state_stream;
|
||||
state_stream.id = stream_id.value();
|
||||
state_stream.next_ssn = stream.next_ssn().value();
|
||||
state.rx.ordered_streams.push_back(std::move(state_stream));
|
||||
}
|
||||
for (const auto& [stream_id, unused] : unordered_streams_) {
|
||||
DcSctpSocketHandoverState::UnorderedStream state_stream;
|
||||
state_stream.id = stream_id.value();
|
||||
state.rx.unordered_streams.push_back(std::move(state_stream));
|
||||
}
|
||||
}
|
||||
|
||||
void TraditionalReassemblyStreams::RestoreFromState(
|
||||
const DcSctpSocketHandoverState& state) {
|
||||
// Validate that the component is in pristine state.
|
||||
RTC_DCHECK(ordered_streams_.empty());
|
||||
RTC_DCHECK(unordered_streams_.empty());
|
||||
|
||||
for (const DcSctpSocketHandoverState::OrderedStream& state_stream :
|
||||
state.rx.ordered_streams) {
|
||||
ordered_streams_.emplace(
|
||||
std::piecewise_construct,
|
||||
std::forward_as_tuple(StreamID(state_stream.id)),
|
||||
std::forward_as_tuple(this, SSN(state_stream.next_ssn)));
|
||||
}
|
||||
for (const DcSctpSocketHandoverState::UnorderedStream& state_stream :
|
||||
state.rx.unordered_streams) {
|
||||
unordered_streams_.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(StreamID(state_stream.id)),
|
||||
std::forward_as_tuple(this));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dcsctp
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef NET_DCSCTP_RX_TRADITIONAL_REASSEMBLY_STREAMS_H_
|
||||
#define NET_DCSCTP_RX_TRADITIONAL_REASSEMBLY_STREAMS_H_
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "api/array_view.h"
|
||||
#include "net/dcsctp/common/sequence_numbers.h"
|
||||
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
|
||||
#include "net/dcsctp/packet/data.h"
|
||||
#include "net/dcsctp/rx/reassembly_streams.h"
|
||||
|
||||
namespace dcsctp {
|
||||
|
||||
// Handles reassembly of incoming data when interleaved message sending
|
||||
// is not enabled on the association, i.e. when RFC8260 is not in use and
|
||||
// RFC4960 is to be followed.
|
||||
class TraditionalReassemblyStreams : public ReassemblyStreams {
|
||||
public:
|
||||
TraditionalReassemblyStreams(absl::string_view log_prefix,
|
||||
OnAssembledMessage on_assembled_message);
|
||||
|
||||
int Add(UnwrappedTSN tsn, Data data) override;
|
||||
|
||||
size_t HandleForwardTsn(
|
||||
UnwrappedTSN new_cumulative_ack_tsn,
|
||||
rtc::ArrayView<const AnyForwardTsnChunk::SkippedStream> skipped_streams)
|
||||
override;
|
||||
|
||||
void ResetStreams(rtc::ArrayView<const StreamID> stream_ids) override;
|
||||
|
||||
HandoverReadinessStatus GetHandoverReadiness() const override;
|
||||
void AddHandoverState(DcSctpSocketHandoverState& state) override;
|
||||
void RestoreFromState(const DcSctpSocketHandoverState& state) override;
|
||||
|
||||
private:
|
||||
using ChunkMap = std::map<UnwrappedTSN, Data>;
|
||||
|
||||
// Base class for `UnorderedStream` and `OrderedStream`.
|
||||
class StreamBase {
|
||||
protected:
|
||||
explicit StreamBase(TraditionalReassemblyStreams* parent)
|
||||
: parent_(*parent) {}
|
||||
|
||||
size_t AssembleMessage(ChunkMap::iterator start, ChunkMap::iterator end);
|
||||
size_t AssembleMessage(UnwrappedTSN tsn, Data data);
|
||||
TraditionalReassemblyStreams& parent_;
|
||||
};
|
||||
|
||||
// Manages all received data for a specific unordered stream, and assembles
|
||||
// messages when possible.
|
||||
class UnorderedStream : StreamBase {
|
||||
public:
|
||||
explicit UnorderedStream(TraditionalReassemblyStreams* parent)
|
||||
: StreamBase(parent) {}
|
||||
int Add(UnwrappedTSN tsn, Data data);
|
||||
// Returns the number of bytes removed from the queue.
|
||||
size_t EraseTo(UnwrappedTSN tsn);
|
||||
bool has_unassembled_chunks() const { return !chunks_.empty(); }
|
||||
|
||||
private:
|
||||
// Given an iterator to any chunk within the map, try to assemble a message
|
||||
// into `reassembled_messages` containing it and - if successful - erase
|
||||
// those chunks from the stream chunks map.
|
||||
//
|
||||
// Returns the number of bytes that were assembled.
|
||||
size_t TryToAssembleMessage(ChunkMap::iterator iter);
|
||||
|
||||
ChunkMap chunks_;
|
||||
};
|
||||
|
||||
// Manages all received data for a specific ordered stream, and assembles
|
||||
// messages when possible.
|
||||
class OrderedStream : StreamBase {
|
||||
public:
|
||||
explicit OrderedStream(TraditionalReassemblyStreams* parent,
|
||||
SSN next_ssn = SSN(0))
|
||||
: StreamBase(parent), next_ssn_(ssn_unwrapper_.Unwrap(next_ssn)) {}
|
||||
int Add(UnwrappedTSN tsn, Data data);
|
||||
size_t EraseTo(SSN ssn);
|
||||
void Reset() {
|
||||
ssn_unwrapper_.Reset();
|
||||
next_ssn_ = ssn_unwrapper_.Unwrap(SSN(0));
|
||||
}
|
||||
SSN next_ssn() const { return next_ssn_.Wrap(); }
|
||||
bool has_unassembled_chunks() const { return !chunks_by_ssn_.empty(); }
|
||||
|
||||
private:
|
||||
// Try to assemble one or several messages in order from the stream.
|
||||
// Returns the number of bytes assembled if a message was assembled.
|
||||
size_t TryToAssembleMessage();
|
||||
size_t TryToAssembleMessages();
|
||||
// Same as above but when inserting the first complete message avoid
|
||||
// insertion into the map.
|
||||
size_t TryToAssembleMessagesFastpath(UnwrappedSSN ssn,
|
||||
UnwrappedTSN tsn,
|
||||
Data data);
|
||||
// This must be an ordered container to be able to iterate in SSN order.
|
||||
std::map<UnwrappedSSN, ChunkMap> chunks_by_ssn_;
|
||||
UnwrappedSSN::Unwrapper ssn_unwrapper_;
|
||||
UnwrappedSSN next_ssn_;
|
||||
};
|
||||
|
||||
const absl::string_view log_prefix_;
|
||||
|
||||
// Callback for when a message has been assembled.
|
||||
const OnAssembledMessage on_assembled_message_;
|
||||
|
||||
// All unordered and ordered streams, managing not-yet-assembled data.
|
||||
std::map<StreamID, UnorderedStream> unordered_streams_;
|
||||
std::map<StreamID, OrderedStream> ordered_streams_;
|
||||
};
|
||||
|
||||
} // namespace dcsctp
|
||||
|
||||
#endif // NET_DCSCTP_RX_TRADITIONAL_REASSEMBLY_STREAMS_H_
|
||||
Loading…
Add table
Add a link
Reference in a new issue