Repo created
This commit is contained in:
parent
81b91f4139
commit
f8c34fa5ee
22732 changed files with 4815320 additions and 2 deletions
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
67
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/context.h
Normal file
67
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/context.h
Normal 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_
|
||||
1818
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/dcsctp_socket.cc
Normal file
1818
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/dcsctp_socket.cc
Normal file
File diff suppressed because it is too large
Load diff
301
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/dcsctp_socket.h
Normal file
301
TMessagesProj/jni/voip/webrtc/net/dcsctp/socket/dcsctp_socket.h
Normal 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
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
||||
Loading…
Add table
Add a link
Reference in a new issue