Repo created

This commit is contained in:
Fr4nz D13trich 2025-11-22 14:04:28 +01:00
parent 81b91f4139
commit f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions

View file

@ -0,0 +1,190 @@
/*
* 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/socket/callback_deferrer.h"
#include "api/make_ref_counted.h"
namespace dcsctp {
void CallbackDeferrer::Prepare() {
RTC_DCHECK(!prepared_);
prepared_ = true;
}
void CallbackDeferrer::TriggerDeferred() {
// Need to swap here. The client may call into the library from within a
// callback, and that might result in adding new callbacks to this instance,
// and the vector can't be modified while iterated on.
RTC_DCHECK(prepared_);
prepared_ = false;
if (deferred_.empty()) {
return;
}
std::vector<std::pair<Callback, CallbackData>> deferred;
// Reserve a small buffer to prevent too much reallocation on growth.
deferred.reserve(8);
deferred.swap(deferred_);
for (auto& [cb, data] : deferred) {
cb(std::move(data), underlying_);
}
}
SendPacketStatus CallbackDeferrer::SendPacketWithStatus(
rtc::ArrayView<const uint8_t> data) {
// Will not be deferred - call directly.
return underlying_.SendPacketWithStatus(data);
}
std::unique_ptr<Timeout> CallbackDeferrer::CreateTimeout(
webrtc::TaskQueueBase::DelayPrecision precision) {
// Will not be deferred - call directly.
return underlying_.CreateTimeout(precision);
}
TimeMs CallbackDeferrer::TimeMillis() {
// This should not be called by the library - it's migrated to `Now()`.
RTC_DCHECK(false);
// Will not be deferred - call directly.
return underlying_.TimeMillis();
}
uint32_t CallbackDeferrer::GetRandomInt(uint32_t low, uint32_t high) {
// Will not be deferred - call directly.
return underlying_.GetRandomInt(low, high);
}
void CallbackDeferrer::OnMessageReceived(DcSctpMessage message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnMessageReceived(absl::get<DcSctpMessage>(std::move(data)));
},
std::move(message));
}
void CallbackDeferrer::OnError(ErrorKind error, absl::string_view message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
Error error = absl::get<Error>(std::move(data));
return cb.OnError(error.error, error.message);
},
Error{error, std::string(message)});
}
void CallbackDeferrer::OnAborted(ErrorKind error, absl::string_view message) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
Error error = absl::get<Error>(std::move(data));
return cb.OnAborted(error.error, error.message);
},
Error{error, std::string(message)});
}
void CallbackDeferrer::OnConnected() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnConnected();
},
absl::monostate{});
}
void CallbackDeferrer::OnClosed() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnClosed();
},
absl::monostate{});
}
void CallbackDeferrer::OnConnectionRestarted() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnConnectionRestarted();
},
absl::monostate{});
}
void CallbackDeferrer::OnStreamsResetFailed(
rtc::ArrayView<const StreamID> outgoing_streams,
absl::string_view reason) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
return cb.OnStreamsResetFailed(stream_reset.streams,
stream_reset.message);
},
StreamReset{{outgoing_streams.begin(), outgoing_streams.end()},
std::string(reason)});
}
void CallbackDeferrer::OnStreamsResetPerformed(
rtc::ArrayView<const StreamID> outgoing_streams) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
return cb.OnStreamsResetPerformed(stream_reset.streams);
},
StreamReset{{outgoing_streams.begin(), outgoing_streams.end()}});
}
void CallbackDeferrer::OnIncomingStreamsReset(
rtc::ArrayView<const StreamID> incoming_streams) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
StreamReset stream_reset = absl::get<StreamReset>(std::move(data));
return cb.OnIncomingStreamsReset(stream_reset.streams);
},
StreamReset{{incoming_streams.begin(), incoming_streams.end()}});
}
void CallbackDeferrer::OnBufferedAmountLow(StreamID stream_id) {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnBufferedAmountLow(absl::get<StreamID>(std::move(data)));
},
stream_id);
}
void CallbackDeferrer::OnTotalBufferedAmountLow() {
RTC_DCHECK(prepared_);
deferred_.emplace_back(
+[](CallbackData data, DcSctpSocketCallbacks& cb) {
return cb.OnTotalBufferedAmountLow();
},
absl::monostate{});
}
void CallbackDeferrer::OnLifecycleMessageExpired(LifecycleId lifecycle_id,
bool maybe_delivered) {
// Will not be deferred - call directly.
underlying_.OnLifecycleMessageExpired(lifecycle_id, maybe_delivered);
}
void CallbackDeferrer::OnLifecycleMessageFullySent(LifecycleId lifecycle_id) {
// Will not be deferred - call directly.
underlying_.OnLifecycleMessageFullySent(lifecycle_id);
}
void CallbackDeferrer::OnLifecycleMessageDelivered(LifecycleId lifecycle_id) {
// Will not be deferred - call directly.
underlying_.OnLifecycleMessageDelivered(lifecycle_id);
}
void CallbackDeferrer::OnLifecycleEnd(LifecycleId lifecycle_id) {
// Will not be deferred - call directly.
underlying_.OnLifecycleEnd(lifecycle_id);
}
} // namespace dcsctp

View file

@ -0,0 +1,116 @@
/*
* 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_SOCKET_CALLBACK_DEFERRER_H_
#define NET_DCSCTP_SOCKET_CALLBACK_DEFERRER_H_
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/variant.h"
#include "api/array_view.h"
#include "api/ref_counted_base.h"
#include "api/scoped_refptr.h"
#include "api/task_queue/task_queue_base.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_socket.h"
namespace dcsctp {
// Defers callbacks until they can be safely triggered.
//
// There are a lot of callbacks from the dcSCTP library to the client,
// such as when messages are received or streams are closed. When the client
// receives these callbacks, the client is expected to be able to call into the
// library - from within the callback. For example, sending a reply message when
// a certain SCTP message has been received, or to reconnect when the connection
// was closed for any reason. This means that the dcSCTP library must always be
// in a consistent and stable state when these callbacks are delivered, and to
// ensure that's the case, callbacks are not immediately delivered from where
// they originate, but instead queued (deferred) by this class. At the end of
// any public API method that may result in callbacks, they are triggered and
// then delivered.
//
// There are a number of exceptions, which is clearly annotated in the API.
class CallbackDeferrer : public DcSctpSocketCallbacks {
public:
class ScopedDeferrer {
public:
explicit ScopedDeferrer(CallbackDeferrer& callback_deferrer)
: callback_deferrer_(callback_deferrer) {
callback_deferrer_.Prepare();
}
~ScopedDeferrer() { callback_deferrer_.TriggerDeferred(); }
private:
CallbackDeferrer& callback_deferrer_;
};
explicit CallbackDeferrer(DcSctpSocketCallbacks& underlying)
: underlying_(underlying) {}
// Implementation of DcSctpSocketCallbacks
SendPacketStatus SendPacketWithStatus(
rtc::ArrayView<const uint8_t> data) override;
std::unique_ptr<Timeout> CreateTimeout(
webrtc::TaskQueueBase::DelayPrecision precision) override;
TimeMs TimeMillis() override;
webrtc::Timestamp Now() override { return underlying_.Now(); }
uint32_t GetRandomInt(uint32_t low, uint32_t high) override;
void OnMessageReceived(DcSctpMessage message) override;
void OnError(ErrorKind error, absl::string_view message) override;
void OnAborted(ErrorKind error, absl::string_view message) override;
void OnConnected() override;
void OnClosed() override;
void OnConnectionRestarted() override;
void OnStreamsResetFailed(rtc::ArrayView<const StreamID> outgoing_streams,
absl::string_view reason) override;
void OnStreamsResetPerformed(
rtc::ArrayView<const StreamID> outgoing_streams) override;
void OnIncomingStreamsReset(
rtc::ArrayView<const StreamID> incoming_streams) override;
void OnBufferedAmountLow(StreamID stream_id) override;
void OnTotalBufferedAmountLow() override;
void OnLifecycleMessageExpired(LifecycleId lifecycle_id,
bool maybe_delivered) override;
void OnLifecycleMessageFullySent(LifecycleId lifecycle_id) override;
void OnLifecycleMessageDelivered(LifecycleId lifecycle_id) override;
void OnLifecycleEnd(LifecycleId lifecycle_id) override;
private:
struct Error {
ErrorKind error;
std::string message;
};
struct StreamReset {
std::vector<StreamID> streams;
std::string message;
};
// Use a pre-sized variant for storage to avoid double heap allocation. This
// variant can hold all cases of stored data.
using CallbackData = absl::
variant<absl::monostate, DcSctpMessage, Error, StreamReset, StreamID>;
using Callback = void (*)(CallbackData, DcSctpSocketCallbacks&);
void Prepare();
void TriggerDeferred();
DcSctpSocketCallbacks& underlying_;
bool prepared_ = false;
std::vector<std::pair<Callback, CallbackData>> deferred_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_CALLBACK_DEFERRER_H_

View file

@ -0,0 +1,32 @@
/*
* 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_SOCKET_CAPABILITIES_H_
#define NET_DCSCTP_SOCKET_CAPABILITIES_H_
#include <cstdint>
namespace dcsctp {
// Indicates what the association supports, meaning that both parties
// support it and that feature can be used.
struct Capabilities {
// RFC3758 Partial Reliability Extension
bool partial_reliability = false;
// RFC8260 Stream Schedulers and User Message Interleaving
bool message_interleaving = false;
// RFC6525 Stream Reconfiguration
bool reconfig = false;
// https://datatracker.ietf.org/doc/draft-ietf-tsvwg-sctp-zero-checksum/
bool zero_checksum = false;
// Negotiated maximum incoming and outgoing stream count.
uint16_t negotiated_maximum_incoming_streams = 0;
uint16_t negotiated_maximum_outgoing_streams = 0;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_CAPABILITIES_H_

View file

@ -0,0 +1,67 @@
/*
* 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_SOCKET_CONTEXT_H_
#define NET_DCSCTP_SOCKET_CONTEXT_H_
#include <cstdint>
#include "absl/strings/string_view.h"
#include "api/units/time_delta.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/public/types.h"
namespace dcsctp {
// A set of helper methods used by handlers to e.g. send packets.
//
// Implemented by the TransmissionControlBlock.
class Context {
public:
virtual ~Context() = default;
// Indicates if a connection has been established.
virtual bool is_connection_established() const = 0;
// Returns this side's initial TSN value.
virtual TSN my_initial_tsn() const = 0;
// Returns the peer's initial TSN value.
virtual TSN peer_initial_tsn() const = 0;
// Returns the socket callbacks.
virtual DcSctpSocketCallbacks& callbacks() const = 0;
// Observes a measured RTT value.
virtual void ObserveRTT(webrtc::TimeDelta rtt_ms) = 0;
// Returns the current Retransmission Timeout (rto) value, in milliseconds.
virtual webrtc::TimeDelta current_rto() const = 0;
// Increments the transmission error counter, given a human readable reason.
virtual bool IncrementTxErrorCounter(absl::string_view reason) = 0;
// Clears the transmission error counter.
virtual void ClearTxErrorCounter() = 0;
// Returns true if there have been too many retransmission errors.
virtual bool HasTooManyTxErrors() const = 0;
// Returns a PacketBuilder, filled in with the correct verification tag.
virtual SctpPacket::Builder PacketBuilder() const = 0;
// Builds the packet from `builder` and sends it.
virtual void Send(SctpPacket::Builder& builder) = 0;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_CONTEXT_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,301 @@
/*
* 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_SOCKET_DCSCTP_SOCKET_H_
#define NET_DCSCTP_SOCKET_DCSCTP_SOCKET_H_
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "net/dcsctp/packet/chunk/abort_chunk.h"
#include "net/dcsctp/packet/chunk/chunk.h"
#include "net/dcsctp/packet/chunk/cookie_ack_chunk.h"
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
#include "net/dcsctp/packet/chunk/data_chunk.h"
#include "net/dcsctp/packet/chunk/data_common.h"
#include "net/dcsctp/packet/chunk/error_chunk.h"
#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/forward_tsn_common.h"
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
#include "net/dcsctp/packet/chunk/idata_chunk.h"
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/init_ack_chunk.h"
#include "net/dcsctp/packet/chunk/init_chunk.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_ack_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_chunk.h"
#include "net/dcsctp/packet/chunk/shutdown_complete_chunk.h"
#include "net/dcsctp/packet/data.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/public/packet_observer.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/callback_deferrer.h"
#include "net/dcsctp/socket/packet_sender.h"
#include "net/dcsctp/socket/state_cookie.h"
#include "net/dcsctp/socket/transmission_control_block.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/retransmission_error_counter.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "net/dcsctp/tx/retransmission_timeout.h"
#include "net/dcsctp/tx/rr_send_queue.h"
namespace dcsctp {
// DcSctpSocket represents a single SCTP socket, to be used over DTLS.
//
// Every dcSCTP is completely isolated from any other socket.
//
// This class manages all packet and chunk dispatching and mainly handles the
// connection sequences (connect, close, shutdown, etc) as well as managing
// the Transmission Control Block (tcb).
//
// This class is thread-compatible.
class DcSctpSocket : public DcSctpSocketInterface {
public:
// Instantiates a DcSctpSocket, which interacts with the world through the
// `callbacks` interface and is configured using `options`.
//
// For debugging, `log_prefix` will prefix all debug logs, and a
// `packet_observer` can be attached to e.g. dump sent and received packets.
DcSctpSocket(absl::string_view log_prefix,
DcSctpSocketCallbacks& callbacks,
std::unique_ptr<PacketObserver> packet_observer,
const DcSctpOptions& options);
DcSctpSocket(const DcSctpSocket&) = delete;
DcSctpSocket& operator=(const DcSctpSocket&) = delete;
// Implementation of `DcSctpSocketInterface`.
void ReceivePacket(rtc::ArrayView<const uint8_t> data) override;
void HandleTimeout(TimeoutID timeout_id) override;
void Connect() override;
void RestoreFromState(const DcSctpSocketHandoverState& state) override;
void Shutdown() override;
void Close() override;
SendStatus Send(DcSctpMessage message,
const SendOptions& send_options) override;
std::vector<SendStatus> SendMany(rtc::ArrayView<DcSctpMessage> messages,
const SendOptions& send_options) override;
ResetStreamsStatus ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) override;
SocketState state() const override;
const DcSctpOptions& options() const override { return options_; }
void SetMaxMessageSize(size_t max_message_size) override;
void SetStreamPriority(StreamID stream_id, StreamPriority priority) override;
StreamPriority GetStreamPriority(StreamID stream_id) const override;
size_t buffered_amount(StreamID stream_id) const override;
size_t buffered_amount_low_threshold(StreamID stream_id) const override;
void SetBufferedAmountLowThreshold(StreamID stream_id, size_t bytes) override;
absl::optional<Metrics> GetMetrics() const override;
HandoverReadinessStatus GetHandoverReadiness() const override;
absl::optional<DcSctpSocketHandoverState> GetHandoverStateAndClose() override;
SctpImplementation peer_implementation() const override {
return metrics_.peer_implementation;
}
// Returns this socket's verification tag, or zero if not yet connected.
VerificationTag verification_tag() const {
return tcb_ != nullptr ? tcb_->my_verification_tag() : VerificationTag(0);
}
private:
// Parameter proposals valid during the connect phase.
struct ConnectParameters {
TSN initial_tsn = TSN(0);
VerificationTag verification_tag = VerificationTag(0);
};
// Detailed state (separate from SocketState, which is the public state).
enum class State {
kClosed,
kCookieWait,
// TCB valid in these:
kCookieEchoed,
kEstablished,
kShutdownPending,
kShutdownSent,
kShutdownReceived,
kShutdownAckSent,
};
// Returns the log prefix used for debug logging.
std::string log_prefix() const;
bool IsConsistent() const;
static constexpr absl::string_view ToString(DcSctpSocket::State state);
void CreateTransmissionControlBlock(const Capabilities& capabilities,
VerificationTag my_verification_tag,
TSN my_initial_tsn,
VerificationTag peer_verification_tag,
TSN peer_initial_tsn,
size_t a_rwnd,
TieTag tie_tag);
// Changes the socket state, given a `reason` (for debugging/logging).
void SetState(State state, absl::string_view reason);
// Closes the association. Note that the TCB will not be valid past this call.
void InternalClose(ErrorKind error, absl::string_view message);
// Closes the association, because of too many retransmission errors.
void CloseConnectionBecauseOfTooManyTransmissionErrors();
// Timer expiration handlers
webrtc::TimeDelta OnInitTimerExpiry();
webrtc::TimeDelta OnCookieTimerExpiry();
webrtc::TimeDelta OnShutdownTimerExpiry();
void OnSentPacket(rtc::ArrayView<const uint8_t> packet,
SendPacketStatus status);
// Sends SHUTDOWN or SHUTDOWN-ACK if the socket is shutting down and if all
// outstanding data has been acknowledged.
void MaybeSendShutdownOrAck();
// If the socket is shutting down, responds SHUTDOWN to any incoming DATA.
void MaybeSendShutdownOnPacketReceived(const SctpPacket& packet);
// If there are streams pending to be reset, send a request to reset them.
void MaybeSendResetStreamsRequest();
// Performs internal processing shared between Send and SendMany.
SendStatus InternalSend(const DcSctpMessage& message,
const SendOptions& send_options);
// Sends a INIT chunk.
void SendInit();
// Sends a SHUTDOWN chunk.
void SendShutdown();
// Sends a SHUTDOWN-ACK chunk.
void SendShutdownAck();
// Validates the SCTP packet, as a whole - not the validity of individual
// chunks within it, as that's done in the different chunk handlers.
bool ValidatePacket(const SctpPacket& packet);
// Parses `payload`, which is a serialized packet that is just going to be
// sent and prints all chunks.
void DebugPrintOutgoing(rtc::ArrayView<const uint8_t> payload);
// Called whenever data has been received, or the cumulative acknowledgment
// TSN has moved, that may result in delivering messages.
void MaybeDeliverMessages();
// Returns true if there is a TCB, and false otherwise (and reports an error).
bool ValidateHasTCB();
// Returns true if the parsing of a chunk of type `T` succeeded. If it didn't,
// it reports an error and returns false.
template <class T>
bool ValidateParseSuccess(const absl::optional<T>& c) {
if (c.has_value()) {
return true;
}
ReportFailedToParseChunk(T::kType);
return false;
}
// Reports failing to have parsed a chunk with the provided `chunk_type`.
void ReportFailedToParseChunk(int chunk_type);
// Called when unknown chunks are received. May report an error.
bool HandleUnrecognizedChunk(const SctpPacket::ChunkDescriptor& descriptor);
// Will dispatch more specific chunk handlers.
bool Dispatch(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming DATA chunks.
void HandleData(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming I-DATA chunks.
void HandleIData(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Common handler for DATA and I-DATA chunks.
void HandleDataCommon(AnyDataChunk& chunk);
// Handles incoming INIT chunks.
void HandleInit(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming INIT-ACK chunks.
void HandleInitAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SACK chunks.
void HandleSack(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming HEARTBEAT chunks.
void HandleHeartbeatRequest(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming HEARTBEAT-ACK chunks.
void HandleHeartbeatAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming ABORT chunks.
void HandleAbort(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming ERROR chunks.
void HandleError(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming COOKIE-ECHO chunks.
void HandleCookieEcho(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles receiving COOKIE-ECHO when there already is a TCB. The return value
// indicates if the processing should continue.
bool HandleCookieEchoWithTCB(const CommonHeader& header,
const StateCookie& cookie);
// Handles incoming COOKIE-ACK chunks.
void HandleCookieAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SHUTDOWN chunks.
void HandleShutdown(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming SHUTDOWN-ACK chunks.
void HandleShutdownAck(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming FORWARD-TSN chunks.
void HandleForwardTsn(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming I-FORWARD-TSN chunks.
void HandleIForwardTsn(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Handles incoming RE-CONFIG chunks.
void HandleReconfig(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
// Common handled for FORWARD-TSN/I-FORWARD-TSN.
void HandleForwardTsnCommon(const AnyForwardTsnChunk& chunk);
// Handles incoming SHUTDOWN-COMPLETE chunks
void HandleShutdownComplete(const CommonHeader& header,
const SctpPacket::ChunkDescriptor& descriptor);
const std::string log_prefix_;
const std::unique_ptr<PacketObserver> packet_observer_;
Metrics metrics_;
DcSctpOptions options_;
// Enqueues callbacks and dispatches them just before returning to the caller.
CallbackDeferrer callbacks_;
TimerManager timer_manager_;
const std::unique_ptr<Timer> t1_init_;
const std::unique_ptr<Timer> t1_cookie_;
const std::unique_ptr<Timer> t2_shutdown_;
// Packets that failed to be sent, but should be retried.
PacketSender packet_sender_;
// The actual SendQueue implementation. As data can be sent on a socket before
// the connection is established, this component is not in the TCB.
RRSendQueue send_queue_;
// Contains verification tag and initial TSN between having sent the INIT
// until the connection is established (there is no TCB at this point).
ConnectParameters connect_params_;
// The socket state.
State state_ = State::kClosed;
// If the connection is established, contains a transmission control block.
std::unique_ptr<TransmissionControlBlock> tcb_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_DCSCTP_SOCKET_H_

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,201 @@
/*
* 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/socket/heartbeat_handler.h"
#include <stddef.h>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/functional/bind_front.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "net/dcsctp/packet/bounded_byte_reader.h"
#include "net/dcsctp/packet/bounded_byte_writer.h"
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
#include "net/dcsctp/packet/parameter/heartbeat_info_parameter.h"
#include "net/dcsctp/packet/parameter/parameter.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/timer/timer.h"
#include "rtc_base/logging.h"
namespace dcsctp {
using ::webrtc::TimeDelta;
using ::webrtc::Timestamp;
// This is stored (in serialized form) as HeartbeatInfoParameter sent in
// HeartbeatRequestChunk and received back in HeartbeatAckChunk. It should be
// well understood that this data may be modified by the peer, so it can't
// be trusted.
//
// It currently only stores a timestamp, in millisecond precision, to allow for
// RTT measurements. If that would be manipulated by the peer, it would just
// result in incorrect RTT measurements, which isn't an issue.
class HeartbeatInfo {
public:
static constexpr size_t kBufferSize = sizeof(uint64_t);
static_assert(kBufferSize == 8, "Unexpected buffer size");
explicit HeartbeatInfo(Timestamp created_at) : created_at_(created_at) {}
std::vector<uint8_t> Serialize() {
uint32_t high_bits = static_cast<uint32_t>(created_at_.ms() >> 32);
uint32_t low_bits = static_cast<uint32_t>(created_at_.ms());
std::vector<uint8_t> data(kBufferSize);
BoundedByteWriter<kBufferSize> writer(data);
writer.Store32<0>(high_bits);
writer.Store32<4>(low_bits);
return data;
}
static absl::optional<HeartbeatInfo> Deserialize(
rtc::ArrayView<const uint8_t> data) {
if (data.size() != kBufferSize) {
RTC_LOG(LS_WARNING) << "Invalid heartbeat info: " << data.size()
<< " bytes";
return absl::nullopt;
}
BoundedByteReader<kBufferSize> reader(data);
uint32_t high_bits = reader.Load32<0>();
uint32_t low_bits = reader.Load32<4>();
uint64_t created_at = static_cast<uint64_t>(high_bits) << 32 | low_bits;
return HeartbeatInfo(Timestamp::Millis(created_at));
}
Timestamp created_at() const { return created_at_; }
private:
const Timestamp created_at_;
};
HeartbeatHandler::HeartbeatHandler(absl::string_view log_prefix,
const DcSctpOptions& options,
Context* context,
TimerManager* timer_manager)
: log_prefix_(log_prefix),
ctx_(context),
timer_manager_(timer_manager),
interval_duration_(options.heartbeat_interval.ToTimeDelta()),
interval_duration_should_include_rtt_(
options.heartbeat_interval_include_rtt),
interval_timer_(timer_manager_->CreateTimer(
"heartbeat-interval",
absl::bind_front(&HeartbeatHandler::OnIntervalTimerExpiry, this),
TimerOptions(interval_duration_,
TimerBackoffAlgorithm::kFixed))),
timeout_timer_(timer_manager_->CreateTimer(
"heartbeat-timeout",
absl::bind_front(&HeartbeatHandler::OnTimeoutTimerExpiry, this),
TimerOptions(options.rto_initial.ToTimeDelta(),
TimerBackoffAlgorithm::kExponential,
/*max_restarts=*/0))) {
// The interval timer must always be running as long as the association is up.
RestartTimer();
}
void HeartbeatHandler::RestartTimer() {
if (interval_duration_.IsZero()) {
// Heartbeating has been disabled.
return;
}
if (interval_duration_should_include_rtt_) {
// The RTT should be used, but it's not easy accessible. The RTO will
// suffice.
interval_timer_->set_duration(
interval_duration_ + ctx_->current_rto());
} else {
interval_timer_->set_duration(interval_duration_);
}
interval_timer_->Start();
}
void HeartbeatHandler::HandleHeartbeatRequest(HeartbeatRequestChunk chunk) {
// https://tools.ietf.org/html/rfc4960#section-8.3
// "The receiver of the HEARTBEAT should immediately respond with a
// HEARTBEAT ACK that contains the Heartbeat Information TLV, together with
// any other received TLVs, copied unchanged from the received HEARTBEAT
// chunk."
ctx_->Send(ctx_->PacketBuilder().Add(
HeartbeatAckChunk(std::move(chunk).extract_parameters())));
}
void HeartbeatHandler::HandleHeartbeatAck(HeartbeatAckChunk chunk) {
timeout_timer_->Stop();
absl::optional<HeartbeatInfoParameter> info_param = chunk.info();
if (!info_param.has_value()) {
ctx_->callbacks().OnError(
ErrorKind::kParseFailed,
"Failed to parse HEARTBEAT-ACK; No Heartbeat Info parameter");
return;
}
absl::optional<HeartbeatInfo> info =
HeartbeatInfo::Deserialize(info_param->info());
if (!info.has_value()) {
ctx_->callbacks().OnError(ErrorKind::kParseFailed,
"Failed to parse HEARTBEAT-ACK; Failed to "
"deserialized Heartbeat info parameter");
return;
}
Timestamp now = ctx_->callbacks().Now();
if (info->created_at() > Timestamp::Zero() && info->created_at() <= now) {
ctx_->ObserveRTT(now - info->created_at());
}
// https://tools.ietf.org/html/rfc4960#section-8.1
// "The counter shall be reset each time ... a HEARTBEAT ACK is received from
// the peer endpoint."
ctx_->ClearTxErrorCounter();
}
TimeDelta HeartbeatHandler::OnIntervalTimerExpiry() {
if (ctx_->is_connection_established()) {
HeartbeatInfo info(ctx_->callbacks().Now());
timeout_timer_->set_duration(ctx_->current_rto());
timeout_timer_->Start();
RTC_DLOG(LS_INFO) << log_prefix_ << "Sending HEARTBEAT with timeout "
<< webrtc::ToString(timeout_timer_->duration());
Parameters parameters = Parameters::Builder()
.Add(HeartbeatInfoParameter(info.Serialize()))
.Build();
ctx_->Send(ctx_->PacketBuilder().Add(
HeartbeatRequestChunk(std::move(parameters))));
} else {
RTC_DLOG(LS_VERBOSE)
<< log_prefix_
<< "Will not send HEARTBEAT when connection not established";
}
return TimeDelta::Zero();
}
TimeDelta HeartbeatHandler::OnTimeoutTimerExpiry() {
// Note that the timeout timer is not restarted. It will be started again when
// the interval timer expires.
RTC_DCHECK(!timeout_timer_->is_running());
ctx_->IncrementTxErrorCounter("HEARTBEAT timeout");
return TimeDelta::Zero();
}
} // namespace dcsctp

View file

@ -0,0 +1,69 @@
/*
* 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_SOCKET_HEARTBEAT_HANDLER_H_
#define NET_DCSCTP_SOCKET_HEARTBEAT_HANDLER_H_
#include <stdint.h>
#include <memory>
#include <string>
#include "absl/strings/string_view.h"
#include "net/dcsctp/packet/chunk/heartbeat_ack_chunk.h"
#include "net/dcsctp/packet/chunk/heartbeat_request_chunk.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/timer/timer.h"
namespace dcsctp {
// HeartbeatHandler handles all logic around sending heartbeats and receiving
// the responses, as well as receiving incoming heartbeat requests.
//
// Heartbeats are sent on idle connections to ensure that the connection is
// still healthy and to measure the RTT. If a number of heartbeats time out,
// the connection will eventually be closed.
class HeartbeatHandler {
public:
HeartbeatHandler(absl::string_view log_prefix,
const DcSctpOptions& options,
Context* context,
TimerManager* timer_manager);
// Called when the heartbeat interval timer should be restarted. This is
// generally done every time data is sent, which makes the timer expire when
// the connection is idle.
void RestartTimer();
// Called on received HeartbeatRequestChunk chunks.
void HandleHeartbeatRequest(HeartbeatRequestChunk chunk);
// Called on received HeartbeatRequestChunk chunks.
void HandleHeartbeatAck(HeartbeatAckChunk chunk);
private:
webrtc::TimeDelta OnIntervalTimerExpiry();
webrtc::TimeDelta OnTimeoutTimerExpiry();
const absl::string_view log_prefix_;
Context* ctx_;
TimerManager* timer_manager_;
// The time for a connection to be idle before a heartbeat is sent.
const webrtc::TimeDelta interval_duration_;
// Adding RTT to the duration will add some jitter, which is good in
// production, but less good in unit tests, which is why it can be disabled.
const bool interval_duration_should_include_rtt_;
const std::unique_ptr<Timer> interval_timer_;
const std::unique_ptr<Timer> timeout_timer_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_HEARTBEAT_HANDLER_H_

View file

@ -0,0 +1,73 @@
/*
* 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_SOCKET_MOCK_CONTEXT_H_
#define NET_DCSCTP_SOCKET_MOCK_CONTEXT_H_
#include <cstdint>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/socket/mock_dcsctp_socket_callbacks.h"
#include "test/gmock.h"
namespace dcsctp {
class MockContext : public Context {
public:
static constexpr TSN MyInitialTsn() { return TSN(990); }
static constexpr TSN PeerInitialTsn() { return TSN(10); }
static constexpr VerificationTag PeerVerificationTag() {
return VerificationTag(0x01234567);
}
explicit MockContext(MockDcSctpSocketCallbacks* callbacks)
: callbacks_(*callbacks) {
ON_CALL(*this, is_connection_established)
.WillByDefault(testing::Return(true));
ON_CALL(*this, my_initial_tsn)
.WillByDefault(testing::Return(MyInitialTsn()));
ON_CALL(*this, peer_initial_tsn)
.WillByDefault(testing::Return(PeerInitialTsn()));
ON_CALL(*this, callbacks).WillByDefault(testing::ReturnRef(callbacks_));
ON_CALL(*this, current_rto)
.WillByDefault(testing::Return(webrtc::TimeDelta::Millis(123)));
ON_CALL(*this, Send).WillByDefault([this](SctpPacket::Builder& builder) {
callbacks_.SendPacketWithStatus(builder.Build());
});
}
MOCK_METHOD(bool, is_connection_established, (), (const, override));
MOCK_METHOD(TSN, my_initial_tsn, (), (const, override));
MOCK_METHOD(TSN, peer_initial_tsn, (), (const, override));
MOCK_METHOD(DcSctpSocketCallbacks&, callbacks, (), (const, override));
MOCK_METHOD(void, ObserveRTT, (webrtc::TimeDelta rtt), (override));
MOCK_METHOD(webrtc::TimeDelta, current_rto, (), (const, override));
MOCK_METHOD(bool,
IncrementTxErrorCounter,
(absl::string_view reason),
(override));
MOCK_METHOD(void, ClearTxErrorCounter, (), (override));
MOCK_METHOD(bool, HasTooManyTxErrors, (), (const, override));
SctpPacket::Builder PacketBuilder() const override {
return SctpPacket::Builder(PeerVerificationTag(), options_);
}
MOCK_METHOD(void, Send, (SctpPacket::Builder & builder), (override));
DcSctpOptions options_;
MockDcSctpSocketCallbacks& callbacks_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_MOCK_CONTEXT_H_

View file

@ -0,0 +1,183 @@
/*
* 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_SOCKET_MOCK_DCSCTP_SOCKET_CALLBACKS_H_
#define NET_DCSCTP_SOCKET_MOCK_DCSCTP_SOCKET_CALLBACKS_H_
#include <cstdint>
#include <deque>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/task_queue/task_queue_base.h"
#include "net/dcsctp/public/dcsctp_message.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/public/timeout.h"
#include "net/dcsctp/public/types.h"
#include "net/dcsctp/timer/fake_timeout.h"
#include "rtc_base/logging.h"
#include "rtc_base/random.h"
#include "test/gmock.h"
namespace dcsctp {
namespace internal {
// It can be argued if a mocked random number generator should be deterministic
// or if it should be have as a "real" random number generator. In this
// implementation, each instantiation of `MockDcSctpSocketCallbacks` will have
// their `GetRandomInt` return different sequences, but each instantiation will
// always generate the same sequence of random numbers. This to make it easier
// to compare logs from tests, but still to let e.g. two different sockets (used
// in the same test) get different random numbers, so that they don't start e.g.
// on the same sequence number. While that isn't an issue in the protocol, it
// just makes debugging harder as the two sockets would look exactly the same.
//
// In a real implementation of `DcSctpSocketCallbacks` the random number
// generator backing `GetRandomInt` should be seeded externally and correctly.
inline int GetUniqueSeed() {
static int seed = 0;
return ++seed;
}
} // namespace internal
class MockDcSctpSocketCallbacks : public DcSctpSocketCallbacks {
public:
explicit MockDcSctpSocketCallbacks(absl::string_view name = "")
: log_prefix_(name.empty() ? "" : std::string(name) + ": "),
random_(internal::GetUniqueSeed()),
timeout_manager_([this]() { return now_; }) {
ON_CALL(*this, SendPacketWithStatus)
.WillByDefault([this](rtc::ArrayView<const uint8_t> data) {
sent_packets_.emplace_back(
std::vector<uint8_t>(data.begin(), data.end()));
return SendPacketStatus::kSuccess;
});
ON_CALL(*this, OnMessageReceived)
.WillByDefault([this](DcSctpMessage message) {
received_messages_.emplace_back(std::move(message));
});
ON_CALL(*this, OnError)
.WillByDefault([this](ErrorKind error, absl::string_view message) {
RTC_LOG(LS_WARNING)
<< log_prefix_ << "Socket error: " << ToString(error) << "; "
<< message;
});
ON_CALL(*this, OnAborted)
.WillByDefault([this](ErrorKind error, absl::string_view message) {
RTC_LOG(LS_WARNING)
<< log_prefix_ << "Socket abort: " << ToString(error) << "; "
<< message;
});
ON_CALL(*this, Now).WillByDefault([this]() { return now_; });
}
MOCK_METHOD(SendPacketStatus,
SendPacketWithStatus,
(rtc::ArrayView<const uint8_t> data),
(override));
std::unique_ptr<Timeout> CreateTimeout(
webrtc::TaskQueueBase::DelayPrecision precision) override {
// The fake timeout manager does not implement |precision|.
return timeout_manager_.CreateTimeout();
}
MOCK_METHOD(webrtc::Timestamp, Now, (), (override));
uint32_t GetRandomInt(uint32_t low, uint32_t high) override {
return random_.Rand(low, high);
}
MOCK_METHOD(void, OnMessageReceived, (DcSctpMessage message), (override));
MOCK_METHOD(void,
OnError,
(ErrorKind error, absl::string_view message),
(override));
MOCK_METHOD(void,
OnAborted,
(ErrorKind error, absl::string_view message),
(override));
MOCK_METHOD(void, OnConnected, (), (override));
MOCK_METHOD(void, OnClosed, (), (override));
MOCK_METHOD(void, OnConnectionRestarted, (), (override));
MOCK_METHOD(void,
OnStreamsResetFailed,
(rtc::ArrayView<const StreamID> outgoing_streams,
absl::string_view reason),
(override));
MOCK_METHOD(void,
OnStreamsResetPerformed,
(rtc::ArrayView<const StreamID> outgoing_streams),
(override));
MOCK_METHOD(void,
OnIncomingStreamsReset,
(rtc::ArrayView<const StreamID> incoming_streams),
(override));
MOCK_METHOD(void, OnBufferedAmountLow, (StreamID stream_id), (override));
MOCK_METHOD(void, OnTotalBufferedAmountLow, (), (override));
MOCK_METHOD(void,
OnLifecycleMessageExpired,
(LifecycleId lifecycle_id, bool maybe_delivered),
(override));
MOCK_METHOD(void,
OnLifecycleMessageFullySent,
(LifecycleId lifecycle_id),
(override));
MOCK_METHOD(void,
OnLifecycleMessageDelivered,
(LifecycleId lifecycle_id),
(override));
MOCK_METHOD(void, OnLifecycleEnd, (LifecycleId lifecycle_id), (override));
bool HasPacket() const { return !sent_packets_.empty(); }
std::vector<uint8_t> ConsumeSentPacket() {
if (sent_packets_.empty()) {
return {};
}
std::vector<uint8_t> ret = std::move(sent_packets_.front());
sent_packets_.pop_front();
return ret;
}
absl::optional<DcSctpMessage> ConsumeReceivedMessage() {
if (received_messages_.empty()) {
return absl::nullopt;
}
DcSctpMessage ret = std::move(received_messages_.front());
received_messages_.pop_front();
return ret;
}
void AdvanceTime(webrtc::TimeDelta duration) { now_ = now_ + duration; }
void SetTime(webrtc::Timestamp now) { now_ = now; }
absl::optional<TimeoutID> GetNextExpiredTimeout() {
return timeout_manager_.GetNextExpiredTimeout();
}
webrtc::TimeDelta GetTimeToNextTimeout() const {
return timeout_manager_.GetTimeToNextTimeout();
}
private:
const std::string log_prefix_;
webrtc::Timestamp now_ = webrtc::Timestamp::Zero();
webrtc::Random random_;
FakeTimeoutManager timeout_manager_;
std::deque<std::vector<uint8_t>> sent_packets_;
std::deque<DcSctpMessage> received_messages_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_MOCK_DCSCTP_SOCKET_CALLBACKS_H_

View file

@ -0,0 +1,48 @@
/*
* 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/socket/packet_sender.h"
#include <utility>
#include <vector>
#include "net/dcsctp/public/types.h"
namespace dcsctp {
PacketSender::PacketSender(DcSctpSocketCallbacks& callbacks,
std::function<void(rtc::ArrayView<const uint8_t>,
SendPacketStatus)> on_sent_packet)
: callbacks_(callbacks), on_sent_packet_(std::move(on_sent_packet)) {}
bool PacketSender::Send(SctpPacket::Builder& builder, bool write_checksum) {
if (builder.empty()) {
return false;
}
std::vector<uint8_t> payload = builder.Build(write_checksum);
SendPacketStatus status = callbacks_.SendPacketWithStatus(payload);
on_sent_packet_(payload, status);
switch (status) {
case SendPacketStatus::kSuccess: {
return true;
}
case SendPacketStatus::kTemporaryFailure: {
// TODO(boivie): Queue this packet to be retried to be sent later.
return false;
}
case SendPacketStatus::kError: {
// Nothing that can be done.
return false;
}
}
}
} // namespace dcsctp

View file

@ -0,0 +1,40 @@
/*
* 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_SOCKET_PACKET_SENDER_H_
#define NET_DCSCTP_SOCKET_PACKET_SENDER_H_
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_socket.h"
namespace dcsctp {
// The PacketSender sends packets to the network using the provided callback
// interface. When an attempt to send a packet is made, the `on_sent_packet`
// callback will be triggered.
class PacketSender {
public:
PacketSender(DcSctpSocketCallbacks& callbacks,
std::function<void(rtc::ArrayView<const uint8_t>,
SendPacketStatus)> on_sent_packet);
// Sends the packet, and returns true if it was sent successfully.
bool Send(SctpPacket::Builder& builder, bool write_checksum = true);
private:
DcSctpSocketCallbacks& callbacks_;
// Callback that will be triggered for every send attempt, indicating the
// status of the operation.
std::function<void(rtc::ArrayView<const uint8_t>, SendPacketStatus)>
on_sent_packet_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_PACKET_SENDER_H_

View file

@ -0,0 +1,88 @@
/*
* 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/socket/state_cookie.h"
#include <cstdint>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/packet/bounded_byte_reader.h"
#include "net/dcsctp/packet/bounded_byte_writer.h"
#include "net/dcsctp/socket/capabilities.h"
#include "rtc_base/logging.h"
namespace dcsctp {
// Magic values, which the state cookie is prefixed with.
constexpr uint32_t kMagic1 = 1684230979;
constexpr uint32_t kMagic2 = 1414541360;
constexpr size_t StateCookie::kCookieSize;
std::vector<uint8_t> StateCookie::Serialize() {
std::vector<uint8_t> cookie;
cookie.resize(kCookieSize);
BoundedByteWriter<kCookieSize> buffer(cookie);
buffer.Store32<0>(kMagic1);
buffer.Store32<4>(kMagic2);
buffer.Store32<8>(*peer_tag_);
buffer.Store32<12>(*my_tag_);
buffer.Store32<16>(*peer_initial_tsn_);
buffer.Store32<20>(*my_initial_tsn_);
buffer.Store32<24>(a_rwnd_);
buffer.Store32<28>(static_cast<uint32_t>(*tie_tag_ >> 32));
buffer.Store32<32>(static_cast<uint32_t>(*tie_tag_));
buffer.Store8<36>(capabilities_.partial_reliability);
buffer.Store8<37>(capabilities_.message_interleaving);
buffer.Store8<38>(capabilities_.reconfig);
buffer.Store16<40>(capabilities_.negotiated_maximum_incoming_streams);
buffer.Store16<42>(capabilities_.negotiated_maximum_outgoing_streams);
buffer.Store8<44>(capabilities_.zero_checksum);
return cookie;
}
absl::optional<StateCookie> StateCookie::Deserialize(
rtc::ArrayView<const uint8_t> cookie) {
if (cookie.size() != kCookieSize) {
RTC_DLOG(LS_WARNING) << "Invalid state cookie: " << cookie.size()
<< " bytes";
return absl::nullopt;
}
BoundedByteReader<kCookieSize> buffer(cookie);
uint32_t magic1 = buffer.Load32<0>();
uint32_t magic2 = buffer.Load32<4>();
if (magic1 != kMagic1 || magic2 != kMagic2) {
RTC_DLOG(LS_WARNING) << "Invalid state cookie; wrong magic";
return absl::nullopt;
}
VerificationTag peer_tag(buffer.Load32<8>());
VerificationTag my_tag(buffer.Load32<12>());
TSN peer_initial_tsn(buffer.Load32<16>());
TSN my_initial_tsn(buffer.Load32<20>());
uint32_t a_rwnd = buffer.Load32<24>();
uint32_t tie_tag_upper = buffer.Load32<28>();
uint32_t tie_tag_lower = buffer.Load32<32>();
TieTag tie_tag(static_cast<uint64_t>(tie_tag_upper) << 32 |
static_cast<uint64_t>(tie_tag_lower));
Capabilities capabilities;
capabilities.partial_reliability = buffer.Load8<36>() != 0;
capabilities.message_interleaving = buffer.Load8<37>() != 0;
capabilities.reconfig = buffer.Load8<38>() != 0;
capabilities.negotiated_maximum_incoming_streams = buffer.Load16<40>();
capabilities.negotiated_maximum_outgoing_streams = buffer.Load16<42>();
capabilities.zero_checksum = buffer.Load8<44>() != 0;
return StateCookie(peer_tag, my_tag, peer_initial_tsn, my_initial_tsn, a_rwnd,
tie_tag, capabilities);
}
} // namespace dcsctp

View file

@ -0,0 +1,75 @@
/*
* 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_SOCKET_STATE_COOKIE_H_
#define NET_DCSCTP_SOCKET_STATE_COOKIE_H_
#include <cstdint>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/socket/capabilities.h"
namespace dcsctp {
// This is serialized as a state cookie and put in INIT_ACK. The client then
// responds with this in COOKIE_ECHO.
//
// NOTE: Expect that the client will modify it to try to exploit the library.
// Do not trust anything in it; no pointers or anything like that.
class StateCookie {
public:
static constexpr size_t kCookieSize = 45;
StateCookie(VerificationTag peer_tag,
VerificationTag my_tag,
TSN peer_initial_tsn,
TSN my_initial_tsn,
uint32_t a_rwnd,
TieTag tie_tag,
Capabilities capabilities)
: peer_tag_(peer_tag),
my_tag_(my_tag),
peer_initial_tsn_(peer_initial_tsn),
my_initial_tsn_(my_initial_tsn),
a_rwnd_(a_rwnd),
tie_tag_(tie_tag),
capabilities_(capabilities) {}
// Returns a serialized version of this cookie.
std::vector<uint8_t> Serialize();
// Deserializes the cookie, and returns absl::nullopt if that failed.
static absl::optional<StateCookie> Deserialize(
rtc::ArrayView<const uint8_t> cookie);
VerificationTag peer_tag() const { return peer_tag_; }
VerificationTag my_tag() const { return my_tag_; }
TSN peer_initial_tsn() const { return peer_initial_tsn_; }
TSN my_initial_tsn() const { return my_initial_tsn_; }
uint32_t a_rwnd() const { return a_rwnd_; }
TieTag tie_tag() const { return tie_tag_; }
const Capabilities& capabilities() const { return capabilities_; }
private:
// Also called "Tag_A" in RFC4960.
const VerificationTag peer_tag_;
// Also called "Tag_Z" in RFC4960.
const VerificationTag my_tag_;
const TSN peer_initial_tsn_;
const TSN my_initial_tsn_;
const uint32_t a_rwnd_;
const TieTag tie_tag_;
const Capabilities capabilities_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_STATE_COOKIE_H_

View 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/socket/stream_reset_handler.h"
#include <cstdint>
#include <memory>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/parameter/add_incoming_streams_request_parameter.h"
#include "net/dcsctp/packet/parameter/add_outgoing_streams_request_parameter.h"
#include "net/dcsctp/packet/parameter/incoming_ssn_reset_request_parameter.h"
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
#include "net/dcsctp/packet/parameter/parameter.h"
#include "net/dcsctp/packet/parameter/reconfiguration_response_parameter.h"
#include "net/dcsctp/packet/parameter/ssn_tsn_reset_request_parameter.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/packet/tlv_trait.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/str_join.h"
namespace dcsctp {
namespace {
using ::webrtc::TimeDelta;
using ResponseResult = ReconfigurationResponseParameter::Result;
bool DescriptorsAre(const std::vector<ParameterDescriptor>& c,
uint16_t e1,
uint16_t e2) {
return (c[0].type == e1 && c[1].type == e2) ||
(c[0].type == e2 && c[1].type == e1);
}
} // namespace
bool StreamResetHandler::Validate(const ReConfigChunk& chunk) {
const Parameters& parameters = chunk.parameters();
// https://tools.ietf.org/html/rfc6525#section-3.1
// "Note that each RE-CONFIG chunk holds at least one parameter
// and at most two parameters. Only the following combinations are allowed:"
std::vector<ParameterDescriptor> descriptors = parameters.descriptors();
if (descriptors.size() == 1) {
if ((descriptors[0].type == OutgoingSSNResetRequestParameter::kType) ||
(descriptors[0].type == IncomingSSNResetRequestParameter::kType) ||
(descriptors[0].type == SSNTSNResetRequestParameter::kType) ||
(descriptors[0].type == AddOutgoingStreamsRequestParameter::kType) ||
(descriptors[0].type == AddIncomingStreamsRequestParameter::kType) ||
(descriptors[0].type == ReconfigurationResponseParameter::kType)) {
return true;
}
} else if (descriptors.size() == 2) {
if (DescriptorsAre(descriptors, OutgoingSSNResetRequestParameter::kType,
IncomingSSNResetRequestParameter::kType) ||
DescriptorsAre(descriptors, AddOutgoingStreamsRequestParameter::kType,
AddIncomingStreamsRequestParameter::kType) ||
DescriptorsAre(descriptors, ReconfigurationResponseParameter::kType,
OutgoingSSNResetRequestParameter::kType) ||
DescriptorsAre(descriptors, ReconfigurationResponseParameter::kType,
ReconfigurationResponseParameter::kType)) {
return true;
}
}
RTC_LOG(LS_WARNING) << "Invalid set of RE-CONFIG parameters";
return false;
}
absl::optional<std::vector<ReconfigurationResponseParameter>>
StreamResetHandler::Process(const ReConfigChunk& chunk) {
if (!Validate(chunk)) {
return absl::nullopt;
}
std::vector<ReconfigurationResponseParameter> responses;
for (const ParameterDescriptor& desc : chunk.parameters().descriptors()) {
switch (desc.type) {
case OutgoingSSNResetRequestParameter::kType:
HandleResetOutgoing(desc, responses);
break;
case IncomingSSNResetRequestParameter::kType:
HandleResetIncoming(desc, responses);
break;
case ReconfigurationResponseParameter::kType:
HandleResponse(desc);
break;
}
}
return responses;
}
void StreamResetHandler::HandleReConfig(ReConfigChunk chunk) {
absl::optional<std::vector<ReconfigurationResponseParameter>> responses =
Process(chunk);
if (!responses.has_value()) {
ctx_->callbacks().OnError(ErrorKind::kParseFailed,
"Failed to parse RE-CONFIG command");
return;
}
if (!responses->empty()) {
SctpPacket::Builder b = ctx_->PacketBuilder();
Parameters::Builder params_builder;
for (const auto& response : *responses) {
params_builder.Add(response);
}
b.Add(ReConfigChunk(params_builder.Build()));
ctx_->Send(b);
}
}
bool StreamResetHandler::ValidateReqSeqNbr(
UnwrappedReconfigRequestSn req_seq_nbr,
std::vector<ReconfigurationResponseParameter>& responses) {
if (req_seq_nbr == last_processed_req_seq_nbr_) {
// https://www.rfc-editor.org/rfc/rfc6525.html#section-5.2.1 "If the
// received RE-CONFIG chunk contains at least one request and based on the
// analysis of the Re-configuration Request Sequence Numbers this is the
// last received RE-CONFIG chunk (i.e., a retransmission), the same
// RE-CONFIG chunk MUST to be sent back in response, as it was earlier."
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "req=" << *req_seq_nbr
<< " already processed, returning result="
<< ToString(last_processed_req_result_);
responses.push_back(ReconfigurationResponseParameter(
req_seq_nbr.Wrap(), last_processed_req_result_));
return false;
}
if (req_seq_nbr != last_processed_req_seq_nbr_.next_value()) {
// Too old, too new, from wrong association etc.
// This is expected to happen when handing over a RTCPeerConnection from one
// server to another. The client will notice this and may decide to close
// old data channels, which may be sent to the wrong (or both) servers
// during a handover.
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "req=" << *req_seq_nbr
<< " bad seq_nbr";
responses.push_back(ReconfigurationResponseParameter(
req_seq_nbr.Wrap(), ResponseResult::kErrorBadSequenceNumber));
return false;
}
return true;
}
void StreamResetHandler::HandleResetOutgoing(
const ParameterDescriptor& descriptor,
std::vector<ReconfigurationResponseParameter>& responses) {
absl::optional<OutgoingSSNResetRequestParameter> req =
OutgoingSSNResetRequestParameter::Parse(descriptor.data);
if (!req.has_value()) {
ctx_->callbacks().OnError(ErrorKind::kParseFailed,
"Failed to parse Outgoing Reset command");
return;
}
UnwrappedReconfigRequestSn request_sn =
incoming_reconfig_request_sn_unwrapper_.Unwrap(
req->request_sequence_number());
if (ValidateReqSeqNbr(request_sn, responses)) {
last_processed_req_seq_nbr_ = request_sn;
if (data_tracker_->IsLaterThanCumulativeAckedTsn(
req->sender_last_assigned_tsn())) {
// https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2
// E2) "If the Sender's Last Assigned TSN is greater than the cumulative
// acknowledgment point, then the endpoint MUST enter 'deferred reset
// processing'."
reassembly_queue_->EnterDeferredReset(req->sender_last_assigned_tsn(),
req->stream_ids());
// "If the endpoint enters 'deferred reset processing', it MUST put a
// Re-configuration Response Parameter into a RE-CONFIG chunk indicating
// 'In progress' and MUST send the RE-CONFIG chunk.
last_processed_req_result_ = ResponseResult::kInProgress;
RTC_DLOG(LS_VERBOSE) << log_prefix_
<< "Reset outgoing; Sender last_assigned="
<< *req->sender_last_assigned_tsn()
<< " - not yet reached -> InProgress";
} else {
// https://datatracker.ietf.org/doc/html/rfc6525#section-5.2.2
// E3) If no stream numbers are listed in the parameter, then all incoming
// streams MUST be reset to 0 as the next expected SSN. If specific stream
// numbers are listed, then only these specific streams MUST be reset to
// 0, and all other non-listed SSNs remain unchanged. E4: Any queued TSNs
// (queued at step E2) MUST now be released and processed normally.
reassembly_queue_->ResetStreamsAndLeaveDeferredReset(req->stream_ids());
ctx_->callbacks().OnIncomingStreamsReset(req->stream_ids());
last_processed_req_result_ = ResponseResult::kSuccessPerformed;
RTC_DLOG(LS_VERBOSE) << log_prefix_
<< "Reset outgoing; Sender last_assigned="
<< *req->sender_last_assigned_tsn()
<< " - reached -> SuccessPerformed";
}
responses.push_back(ReconfigurationResponseParameter(
req->request_sequence_number(), last_processed_req_result_));
}
}
void StreamResetHandler::HandleResetIncoming(
const ParameterDescriptor& descriptor,
std::vector<ReconfigurationResponseParameter>& responses) {
absl::optional<IncomingSSNResetRequestParameter> req =
IncomingSSNResetRequestParameter::Parse(descriptor.data);
if (!req.has_value()) {
ctx_->callbacks().OnError(ErrorKind::kParseFailed,
"Failed to parse Incoming Reset command");
return;
}
UnwrappedReconfigRequestSn request_sn =
incoming_reconfig_request_sn_unwrapper_.Unwrap(
req->request_sequence_number());
if (ValidateReqSeqNbr(request_sn, responses)) {
responses.push_back(ReconfigurationResponseParameter(
req->request_sequence_number(), ResponseResult::kSuccessNothingToDo));
last_processed_req_seq_nbr_ = request_sn;
}
}
void StreamResetHandler::HandleResponse(const ParameterDescriptor& descriptor) {
absl::optional<ReconfigurationResponseParameter> resp =
ReconfigurationResponseParameter::Parse(descriptor.data);
if (!resp.has_value()) {
ctx_->callbacks().OnError(
ErrorKind::kParseFailed,
"Failed to parse Reconfiguration Response command");
return;
}
if (current_request_.has_value() && current_request_->has_been_sent() &&
resp->response_sequence_number() == current_request_->req_seq_nbr()) {
reconfig_timer_->Stop();
switch (resp->result()) {
case ResponseResult::kSuccessNothingToDo:
case ResponseResult::kSuccessPerformed:
RTC_DLOG(LS_VERBOSE)
<< log_prefix_ << "Reset stream success, req_seq_nbr="
<< *current_request_->req_seq_nbr() << ", streams="
<< StrJoin(current_request_->streams(), ",",
[](rtc::StringBuilder& sb, StreamID stream_id) {
sb << *stream_id;
});
ctx_->callbacks().OnStreamsResetPerformed(current_request_->streams());
current_request_ = absl::nullopt;
retransmission_queue_->CommitResetStreams();
break;
case ResponseResult::kInProgress:
RTC_DLOG(LS_VERBOSE)
<< log_prefix_ << "Reset stream still pending, req_seq_nbr="
<< *current_request_->req_seq_nbr() << ", streams="
<< StrJoin(current_request_->streams(), ",",
[](rtc::StringBuilder& sb, StreamID stream_id) {
sb << *stream_id;
});
// Force this request to be sent again, but with new req_seq_nbr.
current_request_->PrepareRetransmission();
reconfig_timer_->set_duration(ctx_->current_rto());
reconfig_timer_->Start();
break;
case ResponseResult::kErrorRequestAlreadyInProgress:
case ResponseResult::kDenied:
case ResponseResult::kErrorWrongSSN:
case ResponseResult::kErrorBadSequenceNumber:
RTC_DLOG(LS_WARNING)
<< log_prefix_ << "Reset stream error=" << ToString(resp->result())
<< ", req_seq_nbr=" << *current_request_->req_seq_nbr()
<< ", streams="
<< StrJoin(current_request_->streams(), ",",
[](rtc::StringBuilder& sb, StreamID stream_id) {
sb << *stream_id;
});
ctx_->callbacks().OnStreamsResetFailed(current_request_->streams(),
ToString(resp->result()));
current_request_ = absl::nullopt;
retransmission_queue_->RollbackResetStreams();
break;
}
}
}
absl::optional<ReConfigChunk> StreamResetHandler::MakeStreamResetRequest() {
// Only send stream resets if there are streams to reset, and no current
// ongoing request (there can only be one at a time), and if the stream
// can be reset.
if (current_request_.has_value() ||
!retransmission_queue_->HasStreamsReadyToBeReset()) {
return absl::nullopt;
}
current_request_.emplace(retransmission_queue_->last_assigned_tsn(),
retransmission_queue_->BeginResetStreams());
reconfig_timer_->set_duration(ctx_->current_rto());
reconfig_timer_->Start();
return MakeReconfigChunk();
}
ReConfigChunk StreamResetHandler::MakeReconfigChunk() {
// The req_seq_nbr will be empty if the request has never been sent before,
// or if it was sent, but the sender responded "in progress", and then the
// req_seq_nbr will be cleared to re-send with a new number. But if the
// request is re-sent due to timeout (reconfig-timer expiring), the same
// req_seq_nbr will be used.
RTC_DCHECK(current_request_.has_value());
if (!current_request_->has_been_sent()) {
current_request_->PrepareToSend(next_outgoing_req_seq_nbr_);
next_outgoing_req_seq_nbr_ =
ReconfigRequestSN(*next_outgoing_req_seq_nbr_ + 1);
}
Parameters::Builder params_builder =
Parameters::Builder().Add(OutgoingSSNResetRequestParameter(
current_request_->req_seq_nbr(), current_request_->req_seq_nbr(),
current_request_->sender_last_assigned_tsn(),
current_request_->streams()));
return ReConfigChunk(params_builder.Build());
}
void StreamResetHandler::ResetStreams(
rtc::ArrayView<const StreamID> outgoing_streams) {
for (StreamID stream_id : outgoing_streams) {
retransmission_queue_->PrepareResetStream(stream_id);
}
}
TimeDelta StreamResetHandler::OnReconfigTimerExpiry() {
if (current_request_->has_been_sent()) {
// There is an outstanding request, which timed out while waiting for a
// response.
if (!ctx_->IncrementTxErrorCounter("RECONFIG timeout")) {
// Timed out. The connection will close after processing the timers.
return TimeDelta::Zero();
}
} else {
// There is no outstanding request, but there is a prepared one. This means
// that the receiver has previously responded "in progress", which resulted
// in retrying the request (but with a new req_seq_nbr) after a while.
}
ctx_->Send(ctx_->PacketBuilder().Add(MakeReconfigChunk()));
return ctx_->current_rto();
}
HandoverReadinessStatus StreamResetHandler::GetHandoverReadiness() const {
HandoverReadinessStatus status;
if (retransmission_queue_->HasStreamsReadyToBeReset()) {
status.Add(HandoverUnreadinessReason::kPendingStreamReset);
}
if (current_request_.has_value()) {
status.Add(HandoverUnreadinessReason::kPendingStreamResetRequest);
}
return status;
}
void StreamResetHandler::AddHandoverState(DcSctpSocketHandoverState& state) {
state.rx.last_completed_reset_req_sn =
last_processed_req_seq_nbr_.Wrap().value();
state.tx.next_reset_req_sn = next_outgoing_req_seq_nbr_.value();
}
} // namespace dcsctp

View 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.
*/
#ifndef NET_DCSCTP_SOCKET_STREAM_RESET_HANDLER_H_
#define NET_DCSCTP_SOCKET_STREAM_RESET_HANDLER_H_
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/functional/bind_front.h"
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "api/array_view.h"
#include "api/units/time_delta.h"
#include "net/dcsctp/common/internal_types.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/parameter/incoming_ssn_reset_request_parameter.h"
#include "net/dcsctp/packet/parameter/outgoing_ssn_reset_request_parameter.h"
#include "net/dcsctp/packet/parameter/reconfiguration_response_parameter.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "rtc_base/containers/flat_set.h"
namespace dcsctp {
// StreamResetHandler handles sending outgoing stream reset requests (to close
// an SCTP stream, which translates to closing a data channel).
//
// It also handles incoming "outgoing stream reset requests", when the peer
// wants to close its data channel.
//
// Resetting streams is an asynchronous operation where the client will request
// a request a stream to be reset, but then it might not be performed exactly at
// this point. First, the sender might need to discard all messages that have
// been enqueued for this stream, or it may select to wait until all have been
// sent. At least, it must wait for the currently sending fragmented message to
// be fully sent, because a stream can't be reset while having received half a
// message. In the stream reset request, the "sender's last assigned TSN" is
// provided, which is simply the TSN for which the receiver should've received
// all messages before this value, before the stream can be reset. Since
// fragments can get lost or sent out-of-order, the receiver of a request may
// not have received all the data just yet, and then it will respond to the
// sender: "In progress". In other words, try again. The sender will then need
// to start a timer and try the very same request again (but with a new sequence
// number) until the receiver successfully performs the operation.
//
// All this can take some time, and may be driven by timers, so the client will
// ultimately be notified using callbacks.
//
// In this implementation, when a stream is reset, the queued but not-yet-sent
// messages will be discarded, but that may change in the future. RFC8831 allows
// both behaviors.
class StreamResetHandler {
public:
StreamResetHandler(absl::string_view log_prefix,
Context* context,
TimerManager* timer_manager,
DataTracker* data_tracker,
ReassemblyQueue* reassembly_queue,
RetransmissionQueue* retransmission_queue,
const DcSctpSocketHandoverState* handover_state = nullptr)
: log_prefix_(log_prefix),
ctx_(context),
data_tracker_(data_tracker),
reassembly_queue_(reassembly_queue),
retransmission_queue_(retransmission_queue),
reconfig_timer_(timer_manager->CreateTimer(
"re-config",
absl::bind_front(&StreamResetHandler::OnReconfigTimerExpiry, this),
TimerOptions(webrtc::TimeDelta::Zero()))),
next_outgoing_req_seq_nbr_(
handover_state
? ReconfigRequestSN(handover_state->tx.next_reset_req_sn)
: ReconfigRequestSN(*ctx_->my_initial_tsn())),
last_processed_req_seq_nbr_(
incoming_reconfig_request_sn_unwrapper_.Unwrap(
handover_state
? ReconfigRequestSN(
handover_state->rx.last_completed_reset_req_sn)
: ReconfigRequestSN(*ctx_->peer_initial_tsn() - 1))),
last_processed_req_result_(
ReconfigurationResponseParameter::Result::kSuccessNothingToDo) {}
// Initiates reset of the provided streams. While there can only be one
// ongoing stream reset request at any time, this method can be called at any
// time and also multiple times. It will enqueue requests that can't be
// directly fulfilled, and will asynchronously process them when any ongoing
// request has completed.
void ResetStreams(rtc::ArrayView<const StreamID> outgoing_streams);
// Creates a Reset Streams request that must be sent if returned. Will start
// the reconfig timer. Will return absl::nullopt if there is no need to
// create a request (no streams to reset) or if there already is an ongoing
// stream reset request that hasn't completed yet.
absl::optional<ReConfigChunk> MakeStreamResetRequest();
// Called when handling and incoming RE-CONFIG chunk.
void HandleReConfig(ReConfigChunk chunk);
HandoverReadinessStatus GetHandoverReadiness() const;
void AddHandoverState(DcSctpSocketHandoverState& state);
private:
using UnwrappedReconfigRequestSn = UnwrappedSequenceNumber<ReconfigRequestSN>;
// Represents a stream request operation. There can only be one ongoing at
// any time, and a sent request may either succeed, fail or result in the
// receiver signaling that it can't process it right now, and then it will be
// retried.
class CurrentRequest {
public:
CurrentRequest(TSN sender_last_assigned_tsn, std::vector<StreamID> streams)
: req_seq_nbr_(absl::nullopt),
sender_last_assigned_tsn_(sender_last_assigned_tsn),
streams_(std::move(streams)) {}
// Returns the current request sequence number, if this request has been
// sent (check `has_been_sent` first). Will return 0 if the request is just
// prepared (or scheduled for retransmission) but not yet sent.
ReconfigRequestSN req_seq_nbr() const {
return req_seq_nbr_.value_or(ReconfigRequestSN(0));
}
// The sender's last assigned TSN, from the retransmission queue. The
// receiver uses this to know when all data up to this TSN has been
// received, to know when to safely reset the stream.
TSN sender_last_assigned_tsn() const { return sender_last_assigned_tsn_; }
// The streams that are to be reset.
const std::vector<StreamID>& streams() const { return streams_; }
// If this request has been sent yet. If not, then it's either because it
// has only been prepared and not yet sent, or because the received couldn't
// apply the request, and then the exact same request will be retried, but
// with a new sequence number.
bool has_been_sent() const { return req_seq_nbr_.has_value(); }
// If the receiver can't apply the request yet (and answered "In Progress"),
// this will be called to prepare the request to be retransmitted at a later
// time.
void PrepareRetransmission() { req_seq_nbr_ = absl::nullopt; }
// If the request hasn't been sent yet, this assigns it a request number.
void PrepareToSend(ReconfigRequestSN new_req_seq_nbr) {
req_seq_nbr_ = new_req_seq_nbr;
}
private:
// If this is set, this request has been sent. If it's not set, the request
// has been prepared, but has not yet been sent. This is typically used when
// the peer responded "in progress" and the same request (but a different
// request number) must be sent again.
absl::optional<ReconfigRequestSN> req_seq_nbr_;
// The sender's (that's us) last assigned TSN, from the retransmission
// queue.
TSN sender_last_assigned_tsn_;
// The streams that are to be reset in this request.
const std::vector<StreamID> streams_;
};
// Called to validate an incoming RE-CONFIG chunk.
bool Validate(const ReConfigChunk& chunk);
// Processes a stream stream reconfiguration chunk and may either return
// absl::nullopt (on protocol errors), or a list of responses - either 0, 1
// or 2.
absl::optional<std::vector<ReconfigurationResponseParameter>> Process(
const ReConfigChunk& chunk);
// Creates the actual RE-CONFIG chunk. A request (which set `current_request`)
// must have been created prior.
ReConfigChunk MakeReconfigChunk();
// Called to validate the `req_seq_nbr`, that it's the next in sequence. If it
// fails to validate, and returns false, it will also add a response to
// `responses`.
bool ValidateReqSeqNbr(
UnwrappedReconfigRequestSn req_seq_nbr,
std::vector<ReconfigurationResponseParameter>& responses);
// Called when this socket receives an outgoing stream reset request. It might
// either be performed straight away, or have to be deferred, and the result
// of that will be put in `responses`.
void HandleResetOutgoing(
const ParameterDescriptor& descriptor,
std::vector<ReconfigurationResponseParameter>& responses);
// Called when this socket receives an incoming stream reset request. This
// isn't really supported, but a successful response is put in `responses`.
void HandleResetIncoming(
const ParameterDescriptor& descriptor,
std::vector<ReconfigurationResponseParameter>& responses);
// Called when receiving a response to an outgoing stream reset request. It
// will either commit the stream resetting, if the operation was successful,
// or will schedule a retry if it was deferred. And if it failed, the
// operation will be rolled back.
void HandleResponse(const ParameterDescriptor& descriptor);
// Expiration handler for the Reconfig timer.
webrtc::TimeDelta OnReconfigTimerExpiry();
const absl::string_view log_prefix_;
Context* ctx_;
DataTracker* data_tracker_;
ReassemblyQueue* reassembly_queue_;
RetransmissionQueue* retransmission_queue_;
UnwrappedReconfigRequestSn::Unwrapper incoming_reconfig_request_sn_unwrapper_;
const std::unique_ptr<Timer> reconfig_timer_;
// The next sequence number for outgoing stream requests.
ReconfigRequestSN next_outgoing_req_seq_nbr_;
// The current stream request operation.
absl::optional<CurrentRequest> current_request_;
// For incoming requests - last processed request sequence number.
UnwrappedReconfigRequestSn last_processed_req_seq_nbr_;
// The result from last processed incoming request
ReconfigurationResponseParameter::Result last_processed_req_result_;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_STREAM_RESET_HANDLER_H_

View file

@ -0,0 +1,338 @@
/*
* 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/socket/transmission_control_block.h"
#include <algorithm>
#include <cstdint>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/optional.h"
#include "api/units/time_delta.h"
#include "net/dcsctp/packet/chunk/data_chunk.h"
#include "net/dcsctp/packet/chunk/forward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/idata_chunk.h"
#include "net/dcsctp/packet/chunk/iforward_tsn_chunk.h"
#include "net/dcsctp/packet/chunk/reconfig_chunk.h"
#include "net/dcsctp/packet/chunk/sack_chunk.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/types.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/capabilities.h"
#include "net/dcsctp/socket/stream_reset_handler.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "net/dcsctp/tx/retransmission_timeout.h"
#include "rtc_base/logging.h"
#include "rtc_base/strings/string_builder.h"
namespace dcsctp {
using ::webrtc::TimeDelta;
using ::webrtc::Timestamp;
TransmissionControlBlock::TransmissionControlBlock(
TimerManager& timer_manager,
absl::string_view log_prefix,
const DcSctpOptions& options,
const Capabilities& capabilities,
DcSctpSocketCallbacks& callbacks,
SendQueue& send_queue,
VerificationTag my_verification_tag,
TSN my_initial_tsn,
VerificationTag peer_verification_tag,
TSN peer_initial_tsn,
size_t a_rwnd,
TieTag tie_tag,
PacketSender& packet_sender,
std::function<bool()> is_connection_established)
: log_prefix_(log_prefix),
options_(options),
timer_manager_(timer_manager),
capabilities_(capabilities),
callbacks_(callbacks),
t3_rtx_(timer_manager_.CreateTimer(
"t3-rtx",
absl::bind_front(&TransmissionControlBlock::OnRtxTimerExpiry, this),
TimerOptions(options.rto_initial.ToTimeDelta(),
TimerBackoffAlgorithm::kExponential,
/*max_restarts=*/absl::nullopt,
options.max_timer_backoff_duration.has_value()
? options.max_timer_backoff_duration->ToTimeDelta()
: TimeDelta::PlusInfinity()))),
delayed_ack_timer_(timer_manager_.CreateTimer(
"delayed-ack",
absl::bind_front(&TransmissionControlBlock::OnDelayedAckTimerExpiry,
this),
TimerOptions(options.delayed_ack_max_timeout.ToTimeDelta(),
TimerBackoffAlgorithm::kExponential,
/*max_restarts=*/0,
/*max_backoff_duration=*/TimeDelta::PlusInfinity(),
webrtc::TaskQueueBase::DelayPrecision::kHigh))),
my_verification_tag_(my_verification_tag),
my_initial_tsn_(my_initial_tsn),
peer_verification_tag_(peer_verification_tag),
peer_initial_tsn_(peer_initial_tsn),
tie_tag_(tie_tag),
is_connection_established_(std::move(is_connection_established)),
packet_sender_(packet_sender),
rto_(options),
tx_error_counter_(log_prefix, options),
data_tracker_(log_prefix, delayed_ack_timer_.get(), peer_initial_tsn),
reassembly_queue_(log_prefix,
peer_initial_tsn,
options.max_receiver_window_buffer_size,
capabilities.message_interleaving),
retransmission_queue_(
log_prefix,
&callbacks_,
my_initial_tsn,
a_rwnd,
send_queue,
absl::bind_front(&TransmissionControlBlock::ObserveRTT, this),
[this]() { tx_error_counter_.Clear(); },
*t3_rtx_,
options,
capabilities.partial_reliability,
capabilities.message_interleaving),
stream_reset_handler_(log_prefix,
this,
&timer_manager,
&data_tracker_,
&reassembly_queue_,
&retransmission_queue_),
heartbeat_handler_(log_prefix, options, this, &timer_manager_) {
send_queue.EnableMessageInterleaving(capabilities.message_interleaving);
}
void TransmissionControlBlock::ObserveRTT(TimeDelta rtt) {
TimeDelta prev_rto = rto_.rto();
rto_.ObserveRTT(rtt);
RTC_DLOG(LS_VERBOSE) << log_prefix_ << "new rtt=" << webrtc::ToString(rtt)
<< ", srtt=" << webrtc::ToString(rto_.srtt())
<< ", rto=" << webrtc::ToString(rto_.rto()) << " ("
<< webrtc::ToString(prev_rto) << ")";
t3_rtx_->set_duration(rto_.rto());
TimeDelta delayed_ack_tmo = std::min(
rto_.rto() * 0.5, options_.delayed_ack_max_timeout.ToTimeDelta());
delayed_ack_timer_->set_duration(delayed_ack_tmo);
}
TimeDelta TransmissionControlBlock::OnRtxTimerExpiry() {
Timestamp now = callbacks_.Now();
RTC_DLOG(LS_INFO) << log_prefix_ << "Timer " << t3_rtx_->name()
<< " has expired";
if (cookie_echo_chunk_.has_value()) {
// In the COOKIE_ECHO state, let the T1-COOKIE timer trigger
// retransmissions, to avoid having two timers doing that.
RTC_DLOG(LS_VERBOSE) << "Not retransmitting as T1-cookie is active.";
} else {
if (IncrementTxErrorCounter("t3-rtx expired")) {
retransmission_queue_.HandleT3RtxTimerExpiry();
SendBufferedPackets(now);
}
}
return TimeDelta::Zero();
}
TimeDelta TransmissionControlBlock::OnDelayedAckTimerExpiry() {
data_tracker_.HandleDelayedAckTimerExpiry();
MaybeSendSack();
return TimeDelta::Zero();
}
void TransmissionControlBlock::MaybeSendSack() {
if (data_tracker_.ShouldSendAck(/*also_if_delayed=*/false)) {
SctpPacket::Builder builder = PacketBuilder();
builder.Add(
data_tracker_.CreateSelectiveAck(reassembly_queue_.remaining_bytes()));
Send(builder);
}
}
void TransmissionControlBlock::MaybeSendForwardTsn(SctpPacket::Builder& builder,
Timestamp now) {
if (now >= limit_forward_tsn_until_ &&
retransmission_queue_.ShouldSendForwardTsn(now)) {
if (capabilities_.message_interleaving) {
builder.Add(retransmission_queue_.CreateIForwardTsn());
} else {
builder.Add(retransmission_queue_.CreateForwardTsn());
}
Send(builder);
// https://datatracker.ietf.org/doc/html/rfc3758
// "IMPLEMENTATION NOTE: An implementation may wish to limit the number of
// duplicate FORWARD TSN chunks it sends by ... waiting a full RTT before
// sending a duplicate FORWARD TSN."
// "Any delay applied to the sending of FORWARD TSN chunk SHOULD NOT exceed
// 200ms and MUST NOT exceed 500ms".
limit_forward_tsn_until_ =
now + std::min(TimeDelta::Millis(200), rto_.srtt());
}
}
void TransmissionControlBlock::MaybeSendFastRetransmit() {
if (!retransmission_queue_.has_data_to_be_fast_retransmitted()) {
return;
}
// https://datatracker.ietf.org/doc/html/rfc4960#section-7.2.4
// "Determine how many of the earliest (i.e., lowest TSN) DATA chunks marked
// for retransmission will fit into a single packet, subject to constraint of
// the path MTU of the destination transport address to which the packet is
// being sent. Call this value K. Retransmit those K DATA chunks in a single
// packet. When a Fast Retransmit is being performed, the sender SHOULD
// ignore the value of cwnd and SHOULD NOT delay retransmission for this
// single packet."
SctpPacket::Builder builder(peer_verification_tag_, options_);
auto chunks = retransmission_queue_.GetChunksForFastRetransmit(
builder.bytes_remaining());
for (auto& [tsn, data] : chunks) {
if (capabilities_.message_interleaving) {
builder.Add(IDataChunk(tsn, std::move(data), false));
} else {
builder.Add(DataChunk(tsn, std::move(data), false));
}
}
Send(builder);
}
void TransmissionControlBlock::SendBufferedPackets(SctpPacket::Builder& builder,
Timestamp now) {
for (int packet_idx = 0;
packet_idx < options_.max_burst && retransmission_queue_.can_send_data();
++packet_idx) {
// Only add control chunks to the first packet that is sent, if sending
// multiple packets in one go (as allowed by the congestion window).
if (packet_idx == 0) {
if (cookie_echo_chunk_.has_value()) {
// https://tools.ietf.org/html/rfc4960#section-5.1
// "The COOKIE ECHO chunk can be bundled with any pending outbound DATA
// chunks, but it MUST be the first chunk in the packet..."
RTC_DCHECK(builder.empty());
builder.Add(*cookie_echo_chunk_);
}
// https://tools.ietf.org/html/rfc4960#section-6
// "Before an endpoint transmits a DATA chunk, if any received DATA
// chunks have not been acknowledged (e.g., due to delayed ack), the
// sender should create a SACK and bundle it with the outbound DATA chunk,
// as long as the size of the final SCTP packet does not exceed the
// current MTU."
if (data_tracker_.ShouldSendAck(/*also_if_delayed=*/true)) {
builder.Add(data_tracker_.CreateSelectiveAck(
reassembly_queue_.remaining_bytes()));
}
MaybeSendForwardTsn(builder, now);
absl::optional<ReConfigChunk> reconfig =
stream_reset_handler_.MakeStreamResetRequest();
if (reconfig.has_value()) {
builder.Add(*reconfig);
}
}
auto chunks =
retransmission_queue_.GetChunksToSend(now, builder.bytes_remaining());
for (auto& [tsn, data] : chunks) {
if (capabilities_.message_interleaving) {
builder.Add(IDataChunk(tsn, std::move(data), false));
} else {
builder.Add(DataChunk(tsn, std::move(data), false));
}
}
// https://www.ietf.org/archive/id/draft-tuexen-tsvwg-sctp-zero-checksum-02.html#section-4.2
// "When an end point sends a packet containing a COOKIE ECHO chunk, it MUST
// include a correct CRC32c checksum in the packet containing the COOKIE
// ECHO chunk."
bool write_checksum =
!capabilities_.zero_checksum || cookie_echo_chunk_.has_value();
if (!packet_sender_.Send(builder, write_checksum)) {
break;
}
if (cookie_echo_chunk_.has_value()) {
// https://tools.ietf.org/html/rfc4960#section-5.1
// "... until the COOKIE ACK is returned the sender MUST NOT send any
// other packets to the peer."
break;
}
}
}
std::string TransmissionControlBlock::ToString() const {
rtc::StringBuilder sb;
sb.AppendFormat(
"verification_tag=%08x, last_cumulative_ack=%u, capabilities=",
*peer_verification_tag_, *data_tracker_.last_cumulative_acked_tsn());
if (capabilities_.partial_reliability) {
sb << "PR,";
}
if (capabilities_.message_interleaving) {
sb << "IL,";
}
if (capabilities_.reconfig) {
sb << "Reconfig,";
}
if (capabilities_.zero_checksum) {
sb << "ZeroChecksum,";
}
sb << " max_in=" << capabilities_.negotiated_maximum_incoming_streams;
sb << " max_out=" << capabilities_.negotiated_maximum_outgoing_streams;
return sb.Release();
}
HandoverReadinessStatus TransmissionControlBlock::GetHandoverReadiness() const {
HandoverReadinessStatus status;
status.Add(data_tracker_.GetHandoverReadiness());
status.Add(stream_reset_handler_.GetHandoverReadiness());
status.Add(reassembly_queue_.GetHandoverReadiness());
status.Add(retransmission_queue_.GetHandoverReadiness());
return status;
}
void TransmissionControlBlock::AddHandoverState(
DcSctpSocketHandoverState& state) {
state.capabilities.partial_reliability = capabilities_.partial_reliability;
state.capabilities.message_interleaving = capabilities_.message_interleaving;
state.capabilities.reconfig = capabilities_.reconfig;
state.capabilities.zero_checksum = capabilities_.zero_checksum;
state.capabilities.negotiated_maximum_incoming_streams =
capabilities_.negotiated_maximum_incoming_streams;
state.capabilities.negotiated_maximum_outgoing_streams =
capabilities_.negotiated_maximum_outgoing_streams;
state.my_verification_tag = my_verification_tag().value();
state.peer_verification_tag = peer_verification_tag().value();
state.my_initial_tsn = my_initial_tsn().value();
state.peer_initial_tsn = peer_initial_tsn().value();
state.tie_tag = tie_tag().value();
data_tracker_.AddHandoverState(state);
stream_reset_handler_.AddHandoverState(state);
reassembly_queue_.AddHandoverState(state);
retransmission_queue_.AddHandoverState(state);
}
void TransmissionControlBlock::RestoreFromState(
const DcSctpSocketHandoverState& state) {
data_tracker_.RestoreFromState(state);
retransmission_queue_.RestoreFromState(state);
reassembly_queue_.RestoreFromState(state);
}
} // namespace dcsctp

View file

@ -0,0 +1,194 @@
/*
* 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_SOCKET_TRANSMISSION_CONTROL_BLOCK_H_
#define NET_DCSCTP_SOCKET_TRANSMISSION_CONTROL_BLOCK_H_
#include <cstdint>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/functional/bind_front.h"
#include "absl/strings/string_view.h"
#include "api/task_queue/task_queue_base.h"
#include "net/dcsctp/common/sequence_numbers.h"
#include "net/dcsctp/packet/chunk/cookie_echo_chunk.h"
#include "net/dcsctp/packet/sctp_packet.h"
#include "net/dcsctp/public/dcsctp_options.h"
#include "net/dcsctp/public/dcsctp_socket.h"
#include "net/dcsctp/rx/data_tracker.h"
#include "net/dcsctp/rx/reassembly_queue.h"
#include "net/dcsctp/socket/capabilities.h"
#include "net/dcsctp/socket/context.h"
#include "net/dcsctp/socket/heartbeat_handler.h"
#include "net/dcsctp/socket/packet_sender.h"
#include "net/dcsctp/socket/stream_reset_handler.h"
#include "net/dcsctp/timer/timer.h"
#include "net/dcsctp/tx/retransmission_error_counter.h"
#include "net/dcsctp/tx/retransmission_queue.h"
#include "net/dcsctp/tx/retransmission_timeout.h"
#include "net/dcsctp/tx/send_queue.h"
namespace dcsctp {
// The TransmissionControlBlock (TCB) represents an open connection to a peer,
// and holds all the resources for that. If the connection is e.g. shutdown,
// closed or restarted, this object will be deleted and/or replaced.
class TransmissionControlBlock : public Context {
public:
TransmissionControlBlock(TimerManager& timer_manager,
absl::string_view log_prefix,
const DcSctpOptions& options,
const Capabilities& capabilities,
DcSctpSocketCallbacks& callbacks,
SendQueue& send_queue,
VerificationTag my_verification_tag,
TSN my_initial_tsn,
VerificationTag peer_verification_tag,
TSN peer_initial_tsn,
size_t a_rwnd,
TieTag tie_tag,
PacketSender& packet_sender,
std::function<bool()> is_connection_established);
// Implementation of `Context`.
bool is_connection_established() const override {
return is_connection_established_();
}
TSN my_initial_tsn() const override { return my_initial_tsn_; }
TSN peer_initial_tsn() const override { return peer_initial_tsn_; }
DcSctpSocketCallbacks& callbacks() const override { return callbacks_; }
void ObserveRTT(webrtc::TimeDelta rtt) override;
webrtc::TimeDelta current_rto() const override { return rto_.rto(); }
bool IncrementTxErrorCounter(absl::string_view reason) override {
return tx_error_counter_.Increment(reason);
}
void ClearTxErrorCounter() override { tx_error_counter_.Clear(); }
SctpPacket::Builder PacketBuilder() const override {
return SctpPacket::Builder(peer_verification_tag_, options_);
}
bool HasTooManyTxErrors() const override {
return tx_error_counter_.IsExhausted();
}
void Send(SctpPacket::Builder& builder) override {
packet_sender_.Send(builder,
/*write_checksum=*/!capabilities_.zero_checksum);
}
// Other accessors
DataTracker& data_tracker() { return data_tracker_; }
ReassemblyQueue& reassembly_queue() { return reassembly_queue_; }
RetransmissionQueue& retransmission_queue() { return retransmission_queue_; }
StreamResetHandler& stream_reset_handler() { return stream_reset_handler_; }
HeartbeatHandler& heartbeat_handler() { return heartbeat_handler_; }
size_t cwnd() const { return retransmission_queue_.cwnd(); }
webrtc::TimeDelta current_srtt() const { return rto_.srtt(); }
// Returns this socket's verification tag, set in all packet headers.
VerificationTag my_verification_tag() const { return my_verification_tag_; }
// Returns the peer's verification tag, which should be in received packets.
VerificationTag peer_verification_tag() const {
return peer_verification_tag_;
}
// All negotiated supported capabilities.
const Capabilities& capabilities() const { return capabilities_; }
// A 64-bit tie-tag, used to e.g. detect reconnections.
TieTag tie_tag() const { return tie_tag_; }
// Sends a SACK, if there is a need to.
void MaybeSendSack();
// Sends a FORWARD-TSN, if it is needed and allowed (rate-limited).
void MaybeSendForwardTsn(SctpPacket::Builder& builder, webrtc::Timestamp now);
// Will be set while the socket is in kCookieEcho state. In this state, there
// can only be a single packet outstanding, and it must contain the COOKIE
// ECHO chunk as the first chunk in that packet, until the COOKIE ACK has been
// received, which will make the socket call `ClearCookieEchoChunk`.
void SetCookieEchoChunk(CookieEchoChunk chunk) {
cookie_echo_chunk_ = std::move(chunk);
}
// Called when the COOKIE ACK chunk has been received, to allow further
// packets to be sent.
void ClearCookieEchoChunk() { cookie_echo_chunk_ = absl::nullopt; }
bool has_cookie_echo_chunk() const { return cookie_echo_chunk_.has_value(); }
void MaybeSendFastRetransmit();
// Fills `builder` (which may already be filled with control chunks) with
// other control and data chunks, and sends packets as much as can be
// allowed by the congestion control algorithm.
void SendBufferedPackets(SctpPacket::Builder& builder, webrtc::Timestamp now);
// As above, but without passing in a builder. If `cookie_echo_chunk_` is
// present, then only one packet will be sent, with this chunk as the first
// chunk.
void SendBufferedPackets(webrtc::Timestamp now) {
SctpPacket::Builder builder(peer_verification_tag_, options_);
SendBufferedPackets(builder, now);
}
// Returns a textual representation of this object, for logging.
std::string ToString() const;
HandoverReadinessStatus GetHandoverReadiness() const;
void AddHandoverState(DcSctpSocketHandoverState& state);
void RestoreFromState(const DcSctpSocketHandoverState& handover_state);
private:
// Will be called when the retransmission timer (t3-rtx) expires.
webrtc::TimeDelta OnRtxTimerExpiry();
// Will be called when the delayed ack timer expires.
webrtc::TimeDelta OnDelayedAckTimerExpiry();
const absl::string_view log_prefix_;
const DcSctpOptions options_;
TimerManager& timer_manager_;
// Negotiated capabilities that both peers support.
const Capabilities capabilities_;
DcSctpSocketCallbacks& callbacks_;
// The data retransmission timer, called t3-rtx in SCTP.
const std::unique_ptr<Timer> t3_rtx_;
// Delayed ack timer, which triggers when acks should be sent (when delayed).
const std::unique_ptr<Timer> delayed_ack_timer_;
const VerificationTag my_verification_tag_;
const TSN my_initial_tsn_;
const VerificationTag peer_verification_tag_;
const TSN peer_initial_tsn_;
// Nonce, used to detect reconnections.
const TieTag tie_tag_;
const std::function<bool()> is_connection_established_;
PacketSender& packet_sender_;
// Rate limiting of FORWARD-TSN. Next can be sent at or after this timestamp.
webrtc::Timestamp limit_forward_tsn_until_ = webrtc::Timestamp::Zero();
RetransmissionTimeout rto_;
RetransmissionErrorCounter tx_error_counter_;
DataTracker data_tracker_;
ReassemblyQueue reassembly_queue_;
RetransmissionQueue retransmission_queue_;
StreamResetHandler stream_reset_handler_;
HeartbeatHandler heartbeat_handler_;
// Only valid when the socket state == State::kCookieEchoed. In this state,
// the socket must wait for COOKIE ACK to continue sending any packets (not
// including a COOKIE ECHO). So if `cookie_echo_chunk_` is present, the
// SendBufferedChunks will always only just send one packet, with this chunk
// as the first chunk in the packet.
absl::optional<CookieEchoChunk> cookie_echo_chunk_ = absl::nullopt;
};
} // namespace dcsctp
#endif // NET_DCSCTP_SOCKET_TRANSMISSION_CONTROL_BLOCK_H_