webrtc_m130/net/dcsctp/tx/send_queue.h
Victor Boivie 9cf825ded9 dcsctp: Abandon correct message on stream reset
Before this CL, a message was identified by the triple (stream_id,
is_unordered, MID) (and yes, the MID is always present in the send
queue, even when interleaved message is not enabled.). So when a chunk
was abandoned due to e.g. having reached the retransmission limit, all
other chunks for that message in the retransmission queue, and all
unsent chunks in the send queue were discarded as well.

This works well, except for the fact that resetting a stream will result
in the MID being set to zero again, which can result in two different
messages having the same identifying triple. And due to the
implementation, both messages would get abandoned.

In WebRTC, an entire data channels is either reliable or unreliable, and
for a message to be abandoned, the channel must be unreliable. So this
means that in the case of stream resets - meaning that a channel was
closed and then reopened, an abandoned message from the old (now closed)
channel would result in abandoning another message sent on the re-opened
data channel.

This CL introduces a new internal property on messages while in the
retransmission and send queue; The "outgoing message id". It's a
monotonically increasing identifier - shared among all streams - that is
never reset to zero in the event of a stream reset. And now a message is
actually only identified by the outgoing message id, but often used
together with the stream identifier, as all data in the send queue is
partitioned by stream. This identifier is 32 bits wide, allowing at most
four billion messages to be in-flight, which is not a limitation, as the
TSN is also 32 bits wide.

Bug: webrtc:14600
Change-Id: I33c23fb0e4bde95327b15d1999e76aa43f5fa7db
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322603
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Victor Boivie <boivie@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40881}
2023-10-06 10:48:33 +00:00

145 lines
6.3 KiB
C++

/*
* 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_TX_SEND_QUEUE_H_
#define NET_DCSCTP_TX_SEND_QUEUE_H_
#include <cstdint>
#include <limits>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/public/types.h"
namespace dcsctp {
class SendQueue {
public:
// Container for a data chunk that is produced by the SendQueue
struct DataToSend {
DataToSend(OutgoingMessageId message_id, Data data)
: message_id(message_id), data(std::move(data)) {}
OutgoingMessageId message_id;
// The data to send, including all parameters.
Data data;
// Partial reliability - RFC3758
MaxRetransmits max_retransmissions = MaxRetransmits::NoLimit();
TimeMs expires_at = TimeMs::InfiniteFuture();
// Lifecycle - set for the last fragment, and `LifecycleId::NotSet()` for
// all other fragments.
LifecycleId lifecycle_id = LifecycleId::NotSet();
};
virtual ~SendQueue() = default;
// TODO(boivie): This interface is obviously missing an "Add" function, but
// that is postponed a bit until the story around how to model message
// prioritization, which is important for any advanced stream scheduler, is
// further clarified.
// Produce a chunk to be sent.
//
// `max_size` refers to how many payload bytes that may be produced, not
// including any headers.
virtual absl::optional<DataToSend> Produce(TimeMs now, size_t max_size) = 0;
// Discards a partially sent message identified by the parameters
// `stream_id` and `message_id`. The `message_id` comes from the returned
// information when having called `Produce`. A partially sent message means
// that it has had at least one fragment of it returned when `Produce` was
// called prior to calling this method).
//
// This is used when a message has been found to be expired (by the partial
// reliability extension), and the retransmission queue will signal the
// receiver that any partially received message fragments should be skipped.
// This means that any remaining fragments in the Send Queue must be removed
// as well so that they are not sent.
//
// This function returns true if this message had unsent fragments still in
// the queue that were discarded, and false if there were no such fragments.
virtual bool Discard(StreamID stream_id, OutgoingMessageId message_id) = 0;
// Prepares the stream to be reset. This is used to close a WebRTC data
// channel and will be signaled to the other side.
//
// Concretely, it discards all whole (not partly sent) messages in the given
// stream and pauses that stream so that future added messages aren't
// produced until `ResumeStreams` is called.
//
// TODO(boivie): Investigate if it really should discard any message at all.
// RFC8831 only mentions that "[RFC6525] also guarantees that all the messages
// are delivered (or abandoned) before the stream is reset."
//
// This method can be called multiple times to add more streams to be
// reset, and paused while they are resetting. This is the first part of the
// two-phase commit protocol to reset streams, where the caller completes the
// procedure by either calling `CommitResetStreams` or `RollbackResetStreams`.
virtual void PrepareResetStream(StreamID stream_id) = 0;
// Indicates if there are any streams that are ready to be reset.
virtual bool HasStreamsReadyToBeReset() const = 0;
// Returns a list of streams that are ready to be included in an outgoing
// stream reset request. Any streams that are returned here must be included
// in an outgoing stream reset request, and there must not be concurrent
// requests. Before calling this method again, you must have called
virtual std::vector<StreamID> GetStreamsReadyToBeReset() = 0;
// Called to commit to reset the streams returned by
// `GetStreamsReadyToBeReset`. It will reset the stream sequence numbers
// (SSNs) and message identifiers (MIDs) and resume the paused streams.
virtual void CommitResetStreams() = 0;
// Called to abort the resetting of streams returned by
// `GetStreamsReadyToBeReset`. Will resume the paused streams without
// resetting the stream sequence numbers (SSNs) or message identifiers (MIDs).
// Note that the non-partial messages that were discarded when calling
// `PrepareResetStreams` will not be recovered, to better match the intention
// from the sender to "close the channel".
virtual void RollbackResetStreams() = 0;
// Resets all message identifier counters (MID, SSN) and makes all partially
// messages be ready to be re-sent in full. This is used when the peer has
// been detected to have restarted and is used to try to minimize the amount
// of data loss. However, data loss cannot be completely guaranteed when a
// peer restarts.
virtual void Reset() = 0;
// Returns the amount of buffered data. This doesn't include packets that are
// e.g. inflight.
virtual size_t buffered_amount(StreamID stream_id) const = 0;
// Returns the total amount of buffer data, for all streams.
virtual size_t total_buffered_amount() const = 0;
// Returns the limit for the `OnBufferedAmountLow` event. Default value is 0.
virtual size_t buffered_amount_low_threshold(StreamID stream_id) const = 0;
// Sets a limit for the `OnBufferedAmountLow` event.
virtual void SetBufferedAmountLowThreshold(StreamID stream_id,
size_t bytes) = 0;
// Configures the send queue to support interleaved message sending as
// described in RFC8260. Every send queue starts with this value set as
// disabled, but can later change it when the capabilities of the connection
// have been negotiated. This affects the behavior of the `Produce` method.
virtual void EnableMessageInterleaving(bool enabled) = 0;
};
} // namespace dcsctp
#endif // NET_DCSCTP_TX_SEND_QUEUE_H_