diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:47:29 +0000 |
commit | 0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch) | |
tree | a31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /third_party/libwebrtc/media/sctp | |
parent | Initial commit. (diff) | |
download | firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip |
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/libwebrtc/media/sctp')
7 files changed, 1282 insertions, 0 deletions
diff --git a/third_party/libwebrtc/media/sctp/OWNERS b/third_party/libwebrtc/media/sctp/OWNERS new file mode 100644 index 0000000000..da2f0178a8 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/OWNERS @@ -0,0 +1,3 @@ +boivie@webrtc.org +deadbeef@webrtc.org +orphis@webrtc.org diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport.cc b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc new file mode 100644 index 0000000000..062360d251 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/dcsctp_transport.cc @@ -0,0 +1,679 @@ +/* + * Copyright 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 "media/sctp/dcsctp_transport.h" + +#include <atomic> +#include <cstdint> +#include <limits> +#include <utility> +#include <vector> + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "media/base/media_channel.h" +#include "net/dcsctp/public/dcsctp_socket_factory.h" +#include "net/dcsctp/public/packet_observer.h" +#include "net/dcsctp/public/text_pcap_packet_observer.h" +#include "net/dcsctp/public/types.h" +#include "p2p/base/packet_transport_internal.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/socket.h" +#include "rtc_base/strings/string_builder.h" +#include "rtc_base/thread.h" +#include "rtc_base/trace_event.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +namespace { +using ::dcsctp::SendPacketStatus; + +// When there is packet loss for a long time, the SCTP retry timers will use +// exponential backoff, which can grow to very long durations and when the +// connection recovers, it may take a long time to reach the new backoff +// duration. By limiting it to a reasonable limit, the time to recover reduces. +constexpr dcsctp::DurationMs kMaxTimerBackoffDuration = + dcsctp::DurationMs(3000); + +enum class WebrtcPPID : dcsctp::PPID::UnderlyingType { + // https://www.rfc-editor.org/rfc/rfc8832.html#section-8.1 + kDCEP = 50, + // https://www.rfc-editor.org/rfc/rfc8831.html#section-8 + kString = 51, + kBinaryPartial = 52, // Deprecated + kBinary = 53, + kStringPartial = 54, // Deprecated + kStringEmpty = 56, + kBinaryEmpty = 57, +}; + +WebrtcPPID ToPPID(DataMessageType message_type, size_t size) { + switch (message_type) { + case webrtc::DataMessageType::kControl: + return WebrtcPPID::kDCEP; + case webrtc::DataMessageType::kText: + return size > 0 ? WebrtcPPID::kString : WebrtcPPID::kStringEmpty; + case webrtc::DataMessageType::kBinary: + return size > 0 ? WebrtcPPID::kBinary : WebrtcPPID::kBinaryEmpty; + } +} + +absl::optional<DataMessageType> ToDataMessageType(dcsctp::PPID ppid) { + switch (static_cast<WebrtcPPID>(ppid.value())) { + case WebrtcPPID::kDCEP: + return webrtc::DataMessageType::kControl; + case WebrtcPPID::kString: + case WebrtcPPID::kStringPartial: + case WebrtcPPID::kStringEmpty: + return webrtc::DataMessageType::kText; + case WebrtcPPID::kBinary: + case WebrtcPPID::kBinaryPartial: + case WebrtcPPID::kBinaryEmpty: + return webrtc::DataMessageType::kBinary; + } + return absl::nullopt; +} + +absl::optional<cricket::SctpErrorCauseCode> ToErrorCauseCode( + dcsctp::ErrorKind error) { + switch (error) { + case dcsctp::ErrorKind::kParseFailed: + return cricket::SctpErrorCauseCode::kUnrecognizedParameters; + case dcsctp::ErrorKind::kPeerReported: + return cricket::SctpErrorCauseCode::kUserInitiatedAbort; + case dcsctp::ErrorKind::kWrongSequence: + case dcsctp::ErrorKind::kProtocolViolation: + return cricket::SctpErrorCauseCode::kProtocolViolation; + case dcsctp::ErrorKind::kResourceExhaustion: + return cricket::SctpErrorCauseCode::kOutOfResource; + case dcsctp::ErrorKind::kTooManyRetries: + case dcsctp::ErrorKind::kUnsupportedOperation: + case dcsctp::ErrorKind::kNoError: + case dcsctp::ErrorKind::kNotConnected: + // No SCTP error cause code matches those + break; + } + return absl::nullopt; +} + +bool IsEmptyPPID(dcsctp::PPID ppid) { + WebrtcPPID webrtc_ppid = static_cast<WebrtcPPID>(ppid.value()); + return webrtc_ppid == WebrtcPPID::kStringEmpty || + webrtc_ppid == WebrtcPPID::kBinaryEmpty; +} +} // namespace + +DcSctpTransport::DcSctpTransport(rtc::Thread* network_thread, + rtc::PacketTransportInternal* transport, + Clock* clock) + : DcSctpTransport(network_thread, + transport, + clock, + std::make_unique<dcsctp::DcSctpSocketFactory>()) {} + +DcSctpTransport::DcSctpTransport( + rtc::Thread* network_thread, + rtc::PacketTransportInternal* transport, + Clock* clock, + std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory) + : network_thread_(network_thread), + transport_(transport), + clock_(clock), + random_(clock_->TimeInMicroseconds()), + socket_factory_(std::move(socket_factory)), + task_queue_timeout_factory_( + *network_thread, + [this]() { return TimeMillis(); }, + [this](dcsctp::TimeoutID timeout_id) { + socket_->HandleTimeout(timeout_id); + }) { + RTC_DCHECK_RUN_ON(network_thread_); + static std::atomic<int> instance_count = 0; + rtc::StringBuilder sb; + sb << debug_name_ << instance_count++; + debug_name_ = sb.Release(); + ConnectTransportSignals(); +} + +DcSctpTransport::~DcSctpTransport() { + if (socket_) { + socket_->Close(); + } +} + +void DcSctpTransport::SetOnConnectedCallback(std::function<void()> callback) { + RTC_DCHECK_RUN_ON(network_thread_); + on_connected_callback_ = std::move(callback); +} + +void DcSctpTransport::SetDataChannelSink(DataChannelSink* sink) { + RTC_DCHECK_RUN_ON(network_thread_); + data_channel_sink_ = sink; + if (data_channel_sink_ && ready_to_send_data_) { + data_channel_sink_->OnReadyToSend(); + } +} + +void DcSctpTransport::SetDtlsTransport( + rtc::PacketTransportInternal* transport) { + RTC_DCHECK_RUN_ON(network_thread_); + DisconnectTransportSignals(); + transport_ = transport; + ConnectTransportSignals(); + MaybeConnectSocket(); +} + +bool DcSctpTransport::Start(int local_sctp_port, + int remote_sctp_port, + int max_message_size) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK(max_message_size > 0); + RTC_DLOG(LS_INFO) << debug_name_ << "->Start(local=" << local_sctp_port + << ", remote=" << remote_sctp_port + << ", max_message_size=" << max_message_size << ")"; + + if (!socket_) { + dcsctp::DcSctpOptions options; + options.local_port = local_sctp_port; + options.remote_port = remote_sctp_port; + options.max_message_size = max_message_size; + options.max_timer_backoff_duration = kMaxTimerBackoffDuration; + // Don't close the connection automatically on too many retransmissions. + options.max_retransmissions = absl::nullopt; + options.max_init_retransmits = absl::nullopt; + + std::unique_ptr<dcsctp::PacketObserver> packet_observer; + if (RTC_LOG_CHECK_LEVEL(LS_VERBOSE)) { + packet_observer = + std::make_unique<dcsctp::TextPcapPacketObserver>(debug_name_); + } + + socket_ = socket_factory_->Create(debug_name_, *this, + std::move(packet_observer), options); + } else { + if (local_sctp_port != socket_->options().local_port || + remote_sctp_port != socket_->options().remote_port) { + RTC_LOG(LS_ERROR) + << debug_name_ << "->Start(local=" << local_sctp_port + << ", remote=" << remote_sctp_port + << "): Can't change ports on already started transport."; + return false; + } + socket_->SetMaxMessageSize(max_message_size); + } + + MaybeConnectSocket(); + + return true; +} + +bool DcSctpTransport::OpenStream(int sid) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_INFO) << debug_name_ << "->OpenStream(" << sid << ")."; + + StreamState stream_state; + stream_states_.insert_or_assign(dcsctp::StreamID(static_cast<uint16_t>(sid)), + stream_state); + return true; +} + +bool DcSctpTransport::ResetStream(int sid) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_INFO) << debug_name_ << "->ResetStream(" << sid << ")."; + if (!socket_) { + RTC_LOG(LS_ERROR) << debug_name_ << "->ResetStream(sid=" << sid + << "): Transport is not started."; + return false; + } + + dcsctp::StreamID streams[1] = {dcsctp::StreamID(static_cast<uint16_t>(sid))}; + + auto it = stream_states_.find(streams[0]); + if (it == stream_states_.end()) { + RTC_LOG(LS_ERROR) << debug_name_ << "->ResetStream(sid=" << sid + << "): Stream is not open."; + return false; + } + + StreamState& stream_state = it->second; + if (stream_state.closure_initiated || stream_state.incoming_reset_done || + stream_state.outgoing_reset_done) { + // The closing procedure was already initiated by the remote, don't do + // anything. + return false; + } + stream_state.closure_initiated = true; + socket_->ResetStreams(streams); + return true; +} + +bool DcSctpTransport::SendData(int sid, + const SendDataParams& params, + const rtc::CopyOnWriteBuffer& payload, + cricket::SendDataResult* result) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_VERBOSE) << debug_name_ << "->SendData(sid=" << sid + << ", type=" << static_cast<int>(params.type) + << ", length=" << payload.size() << ")."; + + if (!socket_) { + RTC_LOG(LS_ERROR) << debug_name_ + << "->SendData(...): Transport is not started."; + *result = cricket::SDR_ERROR; + return false; + } + + // It is possible for a message to be sent from the signaling thread at the + // same time a data-channel is closing, but before the signaling thread is + // aware of it. So we need to keep track of currently active data channels and + // skip sending messages for the ones that are not open or closing. + // The sending errors are not impacting the data channel API contract as + // it is allowed to discard queued messages when the channel is closing. + auto stream_state = + stream_states_.find(dcsctp::StreamID(static_cast<uint16_t>(sid))); + if (stream_state == stream_states_.end()) { + RTC_LOG(LS_VERBOSE) << "Skipping message on non-open stream with sid: " + << sid; + *result = cricket::SDR_ERROR; + return false; + } + + if (stream_state->second.closure_initiated || + stream_state->second.incoming_reset_done || + stream_state->second.outgoing_reset_done) { + RTC_LOG(LS_VERBOSE) << "Skipping message on closing stream with sid: " + << sid; + *result = cricket::SDR_ERROR; + return false; + } + + auto max_message_size = socket_->options().max_message_size; + if (max_message_size > 0 && payload.size() > max_message_size) { + RTC_LOG(LS_WARNING) << debug_name_ + << "->SendData(...): " + "Trying to send packet bigger " + "than the max message size: " + << payload.size() << " vs max of " << max_message_size; + *result = cricket::SDR_ERROR; + return false; + } + + std::vector<uint8_t> message_payload(payload.cdata(), + payload.cdata() + payload.size()); + if (message_payload.empty()) { + // https://www.rfc-editor.org/rfc/rfc8831.html#section-6.6 + // SCTP does not support the sending of empty user messages. Therefore, if + // an empty message has to be sent, the appropriate PPID (WebRTC String + // Empty or WebRTC Binary Empty) is used, and the SCTP user message of one + // zero byte is sent. + message_payload.push_back('\0'); + } + + dcsctp::DcSctpMessage message( + dcsctp::StreamID(static_cast<uint16_t>(sid)), + dcsctp::PPID(static_cast<uint16_t>(ToPPID(params.type, payload.size()))), + std::move(message_payload)); + + dcsctp::SendOptions send_options; + send_options.unordered = dcsctp::IsUnordered(!params.ordered); + if (params.max_rtx_ms.has_value()) { + RTC_DCHECK(*params.max_rtx_ms >= 0 && + *params.max_rtx_ms <= std::numeric_limits<uint16_t>::max()); + send_options.lifetime = dcsctp::DurationMs(*params.max_rtx_ms); + } + if (params.max_rtx_count.has_value()) { + RTC_DCHECK(*params.max_rtx_count >= 0 && + *params.max_rtx_count <= std::numeric_limits<uint16_t>::max()); + send_options.max_retransmissions = *params.max_rtx_count; + } + + auto error = socket_->Send(std::move(message), send_options); + switch (error) { + case dcsctp::SendStatus::kSuccess: + *result = cricket::SDR_SUCCESS; + break; + case dcsctp::SendStatus::kErrorResourceExhaustion: + *result = cricket::SDR_BLOCK; + ready_to_send_data_ = false; + break; + default: + RTC_LOG(LS_ERROR) << debug_name_ + << "->SendData(...): send() failed with error " + << dcsctp::ToString(error) << "."; + *result = cricket::SDR_ERROR; + break; + } + + return *result == cricket::SDR_SUCCESS; +} + +bool DcSctpTransport::ReadyToSendData() { + return ready_to_send_data_; +} + +int DcSctpTransport::max_message_size() const { + if (!socket_) { + RTC_LOG(LS_ERROR) << debug_name_ + << "->max_message_size(...): Transport is not started."; + return 0; + } + return socket_->options().max_message_size; +} + +absl::optional<int> DcSctpTransport::max_outbound_streams() const { + if (!socket_) + return absl::nullopt; + return socket_->options().announced_maximum_outgoing_streams; +} + +absl::optional<int> DcSctpTransport::max_inbound_streams() const { + if (!socket_) + return absl::nullopt; + return socket_->options().announced_maximum_incoming_streams; +} + +void DcSctpTransport::set_debug_name_for_testing(const char* debug_name) { + debug_name_ = debug_name; +} + +SendPacketStatus DcSctpTransport::SendPacketWithStatus( + rtc::ArrayView<const uint8_t> data) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK(socket_); + + if (data.size() > (socket_->options().mtu)) { + RTC_LOG(LS_ERROR) << debug_name_ + << "->SendPacket(...): " + "SCTP seems to have made a packet that is bigger " + "than its official MTU: " + << data.size() << " vs max of " << socket_->options().mtu; + return SendPacketStatus::kError; + } + TRACE_EVENT0("webrtc", "DcSctpTransport::SendPacket"); + + if (!transport_ || !transport_->writable()) + return SendPacketStatus::kError; + + RTC_DLOG(LS_VERBOSE) << debug_name_ << "->SendPacket(length=" << data.size() + << ")"; + + auto result = + transport_->SendPacket(reinterpret_cast<const char*>(data.data()), + data.size(), rtc::PacketOptions(), 0); + + if (result < 0) { + RTC_LOG(LS_WARNING) << debug_name_ << "->SendPacket(length=" << data.size() + << ") failed with error: " << transport_->GetError() + << "."; + + if (rtc::IsBlockingError(transport_->GetError())) { + return SendPacketStatus::kTemporaryFailure; + } + return SendPacketStatus::kError; + } + return SendPacketStatus::kSuccess; +} + +std::unique_ptr<dcsctp::Timeout> DcSctpTransport::CreateTimeout( + webrtc::TaskQueueBase::DelayPrecision precision) { + return task_queue_timeout_factory_.CreateTimeout(precision); +} + +dcsctp::TimeMs DcSctpTransport::TimeMillis() { + return dcsctp::TimeMs(clock_->TimeInMilliseconds()); +} + +uint32_t DcSctpTransport::GetRandomInt(uint32_t low, uint32_t high) { + return random_.Rand(low, high); +} + +void DcSctpTransport::OnTotalBufferedAmountLow() { + RTC_DCHECK_RUN_ON(network_thread_); + if (!ready_to_send_data_) { + ready_to_send_data_ = true; + if (data_channel_sink_) { + data_channel_sink_->OnReadyToSend(); + } + } +} + +void DcSctpTransport::OnMessageReceived(dcsctp::DcSctpMessage message) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_VERBOSE) << debug_name_ << "->OnMessageReceived(sid=" + << message.stream_id().value() + << ", ppid=" << message.ppid().value() + << ", length=" << message.payload().size() << ")."; + cricket::ReceiveDataParams receive_data_params; + receive_data_params.sid = message.stream_id().value(); + auto type = ToDataMessageType(message.ppid()); + if (!type.has_value()) { + RTC_LOG(LS_VERBOSE) << debug_name_ + << "->OnMessageReceived(): Received an unknown PPID " + << message.ppid().value() + << " on an SCTP packet. Dropping."; + } + receive_data_params.type = *type; + // No seq_num available from dcSCTP + receive_data_params.seq_num = 0; + receive_buffer_.Clear(); + if (!IsEmptyPPID(message.ppid())) + receive_buffer_.AppendData(message.payload().data(), + message.payload().size()); + + if (data_channel_sink_) { + data_channel_sink_->OnDataReceived( + receive_data_params.sid, receive_data_params.type, receive_buffer_); + } +} + +void DcSctpTransport::OnError(dcsctp::ErrorKind error, + absl::string_view message) { + if (error == dcsctp::ErrorKind::kResourceExhaustion) { + // Indicates that a message failed to be enqueued, because the send buffer + // is full, which is a very common (and wanted) state for high throughput + // sending/benchmarks. + RTC_LOG(LS_VERBOSE) << debug_name_ + << "->OnError(error=" << dcsctp::ToString(error) + << ", message=" << message << ")."; + } else { + RTC_LOG(LS_ERROR) << debug_name_ + << "->OnError(error=" << dcsctp::ToString(error) + << ", message=" << message << ")."; + } +} + +void DcSctpTransport::OnAborted(dcsctp::ErrorKind error, + absl::string_view message) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_LOG(LS_ERROR) << debug_name_ + << "->OnAborted(error=" << dcsctp::ToString(error) + << ", message=" << message << ")."; + ready_to_send_data_ = false; + RTCError rtc_error(RTCErrorType::OPERATION_ERROR_WITH_DATA, + std::string(message)); + rtc_error.set_error_detail(RTCErrorDetailType::SCTP_FAILURE); + auto code = ToErrorCauseCode(error); + if (code.has_value()) { + rtc_error.set_sctp_cause_code(static_cast<uint16_t>(*code)); + } + if (data_channel_sink_) { + data_channel_sink_->OnTransportClosed(rtc_error); + } +} + +void DcSctpTransport::OnConnected() { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_INFO) << debug_name_ << "->OnConnected()."; + ready_to_send_data_ = true; + if (data_channel_sink_) { + data_channel_sink_->OnReadyToSend(); + } + if (on_connected_callback_) { + on_connected_callback_(); + } +} + +void DcSctpTransport::OnClosed() { + RTC_DLOG(LS_INFO) << debug_name_ << "->OnClosed()."; + ready_to_send_data_ = false; +} + +void DcSctpTransport::OnConnectionRestarted() { + RTC_DLOG(LS_INFO) << debug_name_ << "->OnConnectionRestarted()."; +} + +void DcSctpTransport::OnStreamsResetFailed( + rtc::ArrayView<const dcsctp::StreamID> outgoing_streams, + absl::string_view reason) { + // TODO(orphis): Need a test to check for correct behavior + for (auto& stream_id : outgoing_streams) { + RTC_LOG(LS_WARNING) + << debug_name_ + << "->OnStreamsResetFailed(...): Outgoing stream reset failed" + << ", sid=" << stream_id.value() << ", reason: " << reason << "."; + } +} + +void DcSctpTransport::OnStreamsResetPerformed( + rtc::ArrayView<const dcsctp::StreamID> outgoing_streams) { + RTC_DCHECK_RUN_ON(network_thread_); + for (auto& stream_id : outgoing_streams) { + RTC_LOG(LS_INFO) << debug_name_ + << "->OnStreamsResetPerformed(...): Outgoing stream reset" + << ", sid=" << stream_id.value(); + + auto it = stream_states_.find(stream_id); + if (it == stream_states_.end()) { + // Ignoring an outgoing stream reset for a closed stream + return; + } + + StreamState& stream_state = it->second; + stream_state.outgoing_reset_done = true; + + if (stream_state.incoming_reset_done) { + // When the close was not initiated locally, we can signal the end of the + // data channel close procedure when the remote ACKs the reset. + if (data_channel_sink_) { + data_channel_sink_->OnChannelClosed(stream_id.value()); + } + stream_states_.erase(stream_id); + } + } +} + +void DcSctpTransport::OnIncomingStreamsReset( + rtc::ArrayView<const dcsctp::StreamID> incoming_streams) { + RTC_DCHECK_RUN_ON(network_thread_); + for (auto& stream_id : incoming_streams) { + RTC_LOG(LS_INFO) << debug_name_ + << "->OnIncomingStreamsReset(...): Incoming stream reset" + << ", sid=" << stream_id.value(); + + auto it = stream_states_.find(stream_id); + if (it == stream_states_.end()) + return; + + StreamState& stream_state = it->second; + stream_state.incoming_reset_done = true; + + if (!stream_state.closure_initiated) { + // When receiving an incoming stream reset event for a non local close + // procedure, the transport needs to reset the stream in the other + // direction too. + dcsctp::StreamID streams[1] = {stream_id}; + socket_->ResetStreams(streams); + if (data_channel_sink_) { + data_channel_sink_->OnChannelClosing(stream_id.value()); + } + } + + if (stream_state.outgoing_reset_done) { + // The close procedure that was initiated locally is complete when we + // receive and incoming reset event. + if (data_channel_sink_) { + data_channel_sink_->OnChannelClosed(stream_id.value()); + } + stream_states_.erase(stream_id); + } + } +} + +void DcSctpTransport::ConnectTransportSignals() { + RTC_DCHECK_RUN_ON(network_thread_); + if (!transport_) { + return; + } + transport_->SignalWritableState.connect( + this, &DcSctpTransport::OnTransportWritableState); + transport_->SignalReadPacket.connect(this, + &DcSctpTransport::OnTransportReadPacket); + transport_->SignalClosed.connect(this, &DcSctpTransport::OnTransportClosed); +} + +void DcSctpTransport::DisconnectTransportSignals() { + RTC_DCHECK_RUN_ON(network_thread_); + if (!transport_) { + return; + } + transport_->SignalWritableState.disconnect(this); + transport_->SignalReadPacket.disconnect(this); + transport_->SignalClosed.disconnect(this); +} + +void DcSctpTransport::OnTransportWritableState( + rtc::PacketTransportInternal* transport) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DCHECK_EQ(transport_, transport); + RTC_DLOG(LS_VERBOSE) << debug_name_ + << "->OnTransportWritableState(), writable=" + << transport->writable(); + MaybeConnectSocket(); +} + +void DcSctpTransport::OnTransportReadPacket( + rtc::PacketTransportInternal* transport, + const char* data, + size_t length, + const int64_t& /* packet_time_us */, + int flags) { + RTC_DCHECK_RUN_ON(network_thread_); + if (flags) { + // We are only interested in SCTP packets. + return; + } + + RTC_DLOG(LS_VERBOSE) << debug_name_ + << "->OnTransportReadPacket(), length=" << length; + if (socket_) { + socket_->ReceivePacket(rtc::ArrayView<const uint8_t>( + reinterpret_cast<const uint8_t*>(data), length)); + } +} + +void DcSctpTransport::OnTransportClosed( + rtc::PacketTransportInternal* transport) { + RTC_DCHECK_RUN_ON(network_thread_); + RTC_DLOG(LS_VERBOSE) << debug_name_ << "->OnTransportClosed()."; + if (data_channel_sink_) { + data_channel_sink_->OnTransportClosed({}); + } +} + +void DcSctpTransport::MaybeConnectSocket() { + if (transport_ && transport_->writable() && socket_ && + socket_->state() == dcsctp::SocketState::kClosed) { + socket_->Connect(); + } +} +} // namespace webrtc diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport.h b/third_party/libwebrtc/media/sctp/dcsctp_transport.h new file mode 100644 index 0000000000..f86ac5a23a --- /dev/null +++ b/third_party/libwebrtc/media/sctp/dcsctp_transport.h @@ -0,0 +1,142 @@ +/* + * 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 MEDIA_SCTP_DCSCTP_TRANSPORT_H_ +#define MEDIA_SCTP_DCSCTP_TRANSPORT_H_ + +#include <memory> +#include <string> + +#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 "media/sctp/sctp_transport_internal.h" +#include "net/dcsctp/public/dcsctp_options.h" +#include "net/dcsctp/public/dcsctp_socket.h" +#include "net/dcsctp/public/dcsctp_socket_factory.h" +#include "net/dcsctp/public/types.h" +#include "net/dcsctp/timer/task_queue_timeout.h" +#include "p2p/base/packet_transport_internal.h" +#include "rtc_base/containers/flat_map.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/random.h" +#include "rtc_base/third_party/sigslot/sigslot.h" +#include "rtc_base/thread.h" +#include "rtc_base/thread_annotations.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { + +class DcSctpTransport : public cricket::SctpTransportInternal, + public dcsctp::DcSctpSocketCallbacks, + public sigslot::has_slots<> { + public: + DcSctpTransport(rtc::Thread* network_thread, + rtc::PacketTransportInternal* transport, + Clock* clock); + DcSctpTransport(rtc::Thread* network_thread, + rtc::PacketTransportInternal* transport, + Clock* clock, + std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory); + ~DcSctpTransport() override; + + // cricket::SctpTransportInternal + void SetOnConnectedCallback(std::function<void()> callback) override; + void SetDataChannelSink(DataChannelSink* sink) override; + void SetDtlsTransport(rtc::PacketTransportInternal* transport) override; + bool Start(int local_sctp_port, + int remote_sctp_port, + int max_message_size) override; + bool OpenStream(int sid) override; + bool ResetStream(int sid) override; + bool SendData(int sid, + const SendDataParams& params, + const rtc::CopyOnWriteBuffer& payload, + cricket::SendDataResult* result = nullptr) override; + bool ReadyToSendData() override; + int max_message_size() const override; + absl::optional<int> max_outbound_streams() const override; + absl::optional<int> max_inbound_streams() const override; + void set_debug_name_for_testing(const char* debug_name) override; + + private: + // dcsctp::DcSctpSocketCallbacks + dcsctp::SendPacketStatus SendPacketWithStatus( + rtc::ArrayView<const uint8_t> data) override; + std::unique_ptr<dcsctp::Timeout> CreateTimeout( + webrtc::TaskQueueBase::DelayPrecision precision) override; + dcsctp::TimeMs TimeMillis() override; + uint32_t GetRandomInt(uint32_t low, uint32_t high) override; + void OnTotalBufferedAmountLow() override; + void OnMessageReceived(dcsctp::DcSctpMessage message) override; + void OnError(dcsctp::ErrorKind error, absl::string_view message) override; + void OnAborted(dcsctp::ErrorKind error, absl::string_view message) override; + void OnConnected() override; + void OnClosed() override; + void OnConnectionRestarted() override; + void OnStreamsResetFailed( + rtc::ArrayView<const dcsctp::StreamID> outgoing_streams, + absl::string_view reason) override; + void OnStreamsResetPerformed( + rtc::ArrayView<const dcsctp::StreamID> outgoing_streams) override; + void OnIncomingStreamsReset( + rtc::ArrayView<const dcsctp::StreamID> incoming_streams) override; + + // Transport callbacks + void ConnectTransportSignals(); + void DisconnectTransportSignals(); + void OnTransportWritableState(rtc::PacketTransportInternal* transport); + void OnTransportReadPacket(rtc::PacketTransportInternal* transport, + const char* data, + size_t length, + const int64_t& /* packet_time_us */, + int flags); + void OnTransportClosed(rtc::PacketTransportInternal* transport); + + void MaybeConnectSocket(); + + rtc::Thread* network_thread_; + rtc::PacketTransportInternal* transport_; + Clock* clock_; + Random random_; + + std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory_; + dcsctp::TaskQueueTimeoutFactory task_queue_timeout_factory_; + std::unique_ptr<dcsctp::DcSctpSocketInterface> socket_; + std::string debug_name_ = "DcSctpTransport"; + rtc::CopyOnWriteBuffer receive_buffer_; + + // Used to keep track of the state of data channels. + // Reset needs to happen both ways before signaling the transport + // is closed. + struct StreamState { + // True when the local connection has initiated the reset. + // If a connection receives a reset for a stream that isn't + // already being reset locally, it needs to fire the signal + // SignalClosingProcedureStartedRemotely. + bool closure_initiated = false; + // True when the local connection received OnIncomingStreamsReset + bool incoming_reset_done = false; + // True when the local connection received OnStreamsResetPerformed + bool outgoing_reset_done = false; + }; + + // Map of all currently open or closing data channels + flat_map<dcsctp::StreamID, StreamState> stream_states_ + RTC_GUARDED_BY(network_thread_); + bool ready_to_send_data_ = false; + std::function<void()> on_connected_callback_ RTC_GUARDED_BY(network_thread_); + DataChannelSink* data_channel_sink_ RTC_GUARDED_BY(network_thread_) = nullptr; +}; + +} // namespace webrtc + +#endif // MEDIA_SCTP_DCSCTP_TRANSPORT_H_ diff --git a/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc b/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc new file mode 100644 index 0000000000..08dc2ec0b6 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/dcsctp_transport_unittest.cc @@ -0,0 +1,233 @@ +/* + * Copyright 2022 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "media/sctp/dcsctp_transport.h" + +#include <memory> +#include <utility> + +#include "net/dcsctp/public/mock_dcsctp_socket.h" +#include "net/dcsctp/public/mock_dcsctp_socket_factory.h" +#include "p2p/base/fake_packet_transport.h" +#include "test/gtest.h" + +using ::testing::_; +using ::testing::ByMove; +using ::testing::DoAll; +using ::testing::ElementsAre; +using ::testing::InSequence; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::ReturnPointee; + +namespace webrtc { + +namespace { +class MockDataChannelObserver : public DataChannelSink { + public: + MOCK_METHOD(void, OnConnected, ()); + + // DataChannelSink + MOCK_METHOD(void, + OnDataReceived, + (int, DataMessageType, const rtc::CopyOnWriteBuffer&)); + MOCK_METHOD(void, OnChannelClosing, (int)); + MOCK_METHOD(void, OnChannelClosed, (int)); + MOCK_METHOD(void, OnReadyToSend, ()); + MOCK_METHOD(void, OnTransportClosed, (RTCError)); +}; + +class Peer { + public: + Peer() : fake_packet_transport_("transport"), simulated_clock_(1000) { + auto socket_ptr = std::make_unique<dcsctp::MockDcSctpSocket>(); + socket_ = socket_ptr.get(); + + auto mock_dcsctp_socket_factory = + std::make_unique<dcsctp::MockDcSctpSocketFactory>(); + EXPECT_CALL(*mock_dcsctp_socket_factory, Create) + .Times(1) + .WillOnce(Return(ByMove(std::move(socket_ptr)))); + + sctp_transport_ = std::make_unique<webrtc::DcSctpTransport>( + rtc::Thread::Current(), &fake_packet_transport_, &simulated_clock_, + std::move(mock_dcsctp_socket_factory)); + sctp_transport_->SetDataChannelSink(&observer_); + sctp_transport_->SetOnConnectedCallback( + [this]() { observer_.OnConnected(); }); + } + + rtc::FakePacketTransport fake_packet_transport_; + webrtc::SimulatedClock simulated_clock_; + dcsctp::MockDcSctpSocket* socket_; + std::unique_ptr<webrtc::DcSctpTransport> sctp_transport_; + NiceMock<MockDataChannelObserver> observer_; +}; +} // namespace + +TEST(DcSctpTransportTest, OpenSequence) { + rtc::AutoThread main_thread; + Peer peer_a; + peer_a.fake_packet_transport_.SetWritable(true); + + EXPECT_CALL(*peer_a.socket_, Connect) + .Times(1) + .WillOnce(Invoke(peer_a.sctp_transport_.get(), + &dcsctp::DcSctpSocketCallbacks::OnConnected)); + EXPECT_CALL(peer_a.observer_, OnReadyToSend); + EXPECT_CALL(peer_a.observer_, OnConnected); + + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); +} + +// Tests that the close sequence invoked from one end results in the stream to +// be reset from both ends and all the proper signals are sent. +TEST(DcSctpTransportTest, CloseSequence) { + rtc::AutoThread main_thread; + Peer peer_a; + Peer peer_b; + peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_, + false); + { + InSequence sequence; + + EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1)))) + .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed)); + + EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1)))) + .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed)); + + EXPECT_CALL(peer_a.observer_, OnChannelClosing(1)).Times(0); + EXPECT_CALL(peer_b.observer_, OnChannelClosing(1)); + EXPECT_CALL(peer_a.observer_, OnChannelClosed(1)); + EXPECT_CALL(peer_b.observer_, OnChannelClosed(1)); + } + + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); + peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024); + peer_a.sctp_transport_->OpenStream(1); + peer_b.sctp_transport_->OpenStream(1); + peer_a.sctp_transport_->ResetStream(1); + + // Simulate the callbacks from the stream resets + dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)}; + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get()) + ->OnStreamsResetPerformed(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get()) + ->OnIncomingStreamsReset(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get()) + ->OnIncomingStreamsReset(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get()) + ->OnStreamsResetPerformed(streams); +} + +// Tests that the close sequence initiated from both peers at the same time +// terminates properly. Both peers will think they initiated it, so no +// OnClosingProcedureStartedRemotely should be called. +TEST(DcSctpTransportTest, CloseSequenceSimultaneous) { + rtc::AutoThread main_thread; + Peer peer_a; + Peer peer_b; + peer_a.fake_packet_transport_.SetDestination(&peer_b.fake_packet_transport_, + false); + { + InSequence sequence; + + EXPECT_CALL(*peer_a.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1)))) + .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed)); + + EXPECT_CALL(*peer_b.socket_, ResetStreams(ElementsAre(dcsctp::StreamID(1)))) + .WillOnce(Return(dcsctp::ResetStreamsStatus::kPerformed)); + + EXPECT_CALL(peer_a.observer_, OnChannelClosing(1)).Times(0); + EXPECT_CALL(peer_b.observer_, OnChannelClosing(1)).Times(0); + EXPECT_CALL(peer_a.observer_, OnChannelClosed(1)); + EXPECT_CALL(peer_b.observer_, OnChannelClosed(1)); + } + + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); + peer_b.sctp_transport_->Start(5000, 5000, 256 * 1024); + peer_a.sctp_transport_->OpenStream(1); + peer_b.sctp_transport_->OpenStream(1); + peer_a.sctp_transport_->ResetStream(1); + peer_b.sctp_transport_->ResetStream(1); + + // Simulate the callbacks from the stream resets + dcsctp::StreamID streams[1] = {dcsctp::StreamID(1)}; + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get()) + ->OnStreamsResetPerformed(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get()) + ->OnStreamsResetPerformed(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_a.sctp_transport_.get()) + ->OnIncomingStreamsReset(streams); + static_cast<dcsctp::DcSctpSocketCallbacks*>(peer_b.sctp_transport_.get()) + ->OnIncomingStreamsReset(streams); +} + +TEST(DcSctpTransportTest, DiscardMessageClosedChannel) { + rtc::AutoThread main_thread; + Peer peer_a; + + EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0); + + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); + + cricket::SendDataResult result; + SendDataParams params; + rtc::CopyOnWriteBuffer payload; + bool send_data_return = + peer_a.sctp_transport_->SendData(1, params, payload, &result); + EXPECT_FALSE(send_data_return); + EXPECT_EQ(cricket::SDR_ERROR, result); +} + +TEST(DcSctpTransportTest, DiscardMessageClosingChannel) { + rtc::AutoThread main_thread; + Peer peer_a; + + EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(0); + + peer_a.sctp_transport_->OpenStream(1); + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); + peer_a.sctp_transport_->ResetStream(1); + + cricket::SendDataResult result; + SendDataParams params; + rtc::CopyOnWriteBuffer payload; + + bool send_data_return = + peer_a.sctp_transport_->SendData(1, params, payload, &result); + EXPECT_FALSE(send_data_return); + EXPECT_EQ(cricket::SDR_ERROR, result); +} + +TEST(DcSctpTransportTest, SendDataOpenChannel) { + rtc::AutoThread main_thread; + Peer peer_a; + dcsctp::DcSctpOptions options; + + EXPECT_CALL(*peer_a.socket_, Send(_, _)).Times(1); + EXPECT_CALL(*peer_a.socket_, options()).WillOnce(ReturnPointee(&options)); + + peer_a.sctp_transport_->OpenStream(1); + peer_a.sctp_transport_->Start(5000, 5000, 256 * 1024); + + cricket::SendDataResult result; + SendDataParams params; + rtc::CopyOnWriteBuffer payload; + + bool send_data_return = + peer_a.sctp_transport_->SendData(1, params, payload, &result); + EXPECT_TRUE(send_data_return); + EXPECT_EQ(cricket::SDR_SUCCESS, result); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc b/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc new file mode 100644 index 0000000000..457bc5f889 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/sctp_transport_factory.cc @@ -0,0 +1,38 @@ +/* + * Copyright 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 "media/sctp/sctp_transport_factory.h" + +#include "rtc_base/system/unused.h" + +#ifdef WEBRTC_HAVE_DCSCTP +#include "media/sctp/dcsctp_transport.h" // nogncheck +#include "system_wrappers/include/clock.h" // nogncheck +#endif + +namespace cricket { + +SctpTransportFactory::SctpTransportFactory(rtc::Thread* network_thread) + : network_thread_(network_thread) { + RTC_UNUSED(network_thread_); +} + +std::unique_ptr<SctpTransportInternal> +SctpTransportFactory::CreateSctpTransport( + rtc::PacketTransportInternal* transport) { + std::unique_ptr<SctpTransportInternal> result; +#ifdef WEBRTC_HAVE_DCSCTP + result = std::unique_ptr<SctpTransportInternal>(new webrtc::DcSctpTransport( + network_thread_, transport, webrtc::Clock::GetRealTimeClock())); +#endif + return result; +} + +} // namespace cricket diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_factory.h b/third_party/libwebrtc/media/sctp/sctp_transport_factory.h new file mode 100644 index 0000000000..4fff214129 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/sctp_transport_factory.h @@ -0,0 +1,35 @@ +/* + * 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 MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H_ +#define MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H_ + +#include <memory> + +#include "api/transport/sctp_transport_factory_interface.h" +#include "media/sctp/sctp_transport_internal.h" +#include "rtc_base/thread.h" + +namespace cricket { + +class SctpTransportFactory : public webrtc::SctpTransportFactoryInterface { + public: + explicit SctpTransportFactory(rtc::Thread* network_thread); + + std::unique_ptr<SctpTransportInternal> CreateSctpTransport( + rtc::PacketTransportInternal* transport) override; + + private: + rtc::Thread* network_thread_; +}; + +} // namespace cricket + +#endif // MEDIA_SCTP_SCTP_TRANSPORT_FACTORY_H__ diff --git a/third_party/libwebrtc/media/sctp/sctp_transport_internal.h b/third_party/libwebrtc/media/sctp/sctp_transport_internal.h new file mode 100644 index 0000000000..38da554911 --- /dev/null +++ b/third_party/libwebrtc/media/sctp/sctp_transport_internal.h @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016 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 MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_ +#define MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_ + +// TODO(deadbeef): Move SCTP code out of media/, and make it not depend on +// anything in media/. + +#include <memory> +#include <string> +#include <vector> + +#include "api/transport/data_channel_transport_interface.h" +// For SendDataParams/ReceiveDataParams. +// TODO(deadbeef): Use something else for SCTP. It's confusing that we use an +// SSRC field for SID. +#include "media/base/media_channel.h" +#include "p2p/base/packet_transport_internal.h" +#include "rtc_base/copy_on_write_buffer.h" +#include "rtc_base/thread.h" + +namespace cricket { + +// Constants that are important to API users +// The size of the SCTP association send buffer. 256kB, the usrsctp default. +constexpr int kSctpSendBufferSize = 256 * 1024; + +// The number of outgoing streams that we'll negotiate. Since stream IDs (SIDs) +// are 0-based, the highest usable SID is 1023. +// +// It's recommended to use the maximum of 65535 in: +// https://tools.ietf.org/html/draft-ietf-rtcweb-data-channel-13#section-6.2 +// However, we use 1024 in order to save memory. usrsctp allocates 104 bytes +// for each pair of incoming/outgoing streams (on a 64-bit system), so 65535 +// streams would waste ~6MB. +// +// Note: "max" and "min" here are inclusive. +constexpr uint16_t kMaxSctpStreams = 1024; +constexpr uint16_t kMaxSctpSid = kMaxSctpStreams - 1; +constexpr uint16_t kMinSctpSid = 0; + +// This is the default SCTP port to use. It is passed along the wire and the +// connectee and connector must be using the same port. It is not related to the +// ports at the IP level. (Corresponds to: sockaddr_conn.sconn_port in +// usrsctp.h) +const int kSctpDefaultPort = 5000; + +// Error cause codes defined at +// https://www.iana.org/assignments/sctp-parameters/sctp-parameters.xhtml#sctp-parameters-24 +enum class SctpErrorCauseCode : uint16_t { + kInvalidStreamIdentifier = 1, + kMissingMandatoryParameter = 2, + kStaleCookieError = 3, + kOutOfResource = 4, + kUnresolvableAddress = 5, + kUnrecognizedChunkType = 6, + kInvalidMandatoryParameter = 7, + kUnrecognizedParameters = 8, + kNoUserData = 9, + kCookieReceivedWhileShuttingDown = 10, + kRestartWithNewAddresses = 11, + kUserInitiatedAbort = 12, + kProtocolViolation = 13, +}; + +// Abstract SctpTransport interface for use internally (by PeerConnection etc.). +// Exists to allow mock/fake SctpTransports to be created. +class SctpTransportInternal { + public: + virtual ~SctpTransportInternal() {} + + virtual void SetOnConnectedCallback(std::function<void()> callback) = 0; + virtual void SetDataChannelSink(webrtc::DataChannelSink* sink) = 0; + + // Changes what underlying DTLS transport is uses. Used when switching which + // bundled transport the SctpTransport uses. + virtual void SetDtlsTransport(rtc::PacketTransportInternal* transport) = 0; + + // When Start is called, connects as soon as possible; this can be called + // before DTLS completes, in which case the connection will begin when DTLS + // completes. This method can be called multiple times, though not if either + // of the ports are changed. + // + // `local_sctp_port` and `remote_sctp_port` are passed along the wire and the + // listener and connector must be using the same port. They are not related + // to the ports at the IP level. If set to -1, we default to + // kSctpDefaultPort. + // `max_message_size_` sets the max message size on the connection. + // It must be smaller than or equal to kSctpSendBufferSize. + // It can be changed by a secons Start() call. + // + // TODO(deadbeef): Support calling Start with different local/remote ports + // and create a new association? Not clear if this is something we need to + // support though. See: https://github.com/w3c/webrtc-pc/issues/979 + virtual bool Start(int local_sctp_port, + int remote_sctp_port, + int max_message_size) = 0; + + // NOTE: Initially there was a "Stop" method here, but it was never used, so + // it was removed. + + // Informs SctpTransport that `sid` will start being used. Returns false if + // it is impossible to use `sid`, or if it's already in use. + // Until calling this, can't send data using `sid`. + // TODO(deadbeef): Actually implement the "returns false if `sid` can't be + // used" part. See: + // https://bugs.chromium.org/p/chromium/issues/detail?id=619849 + virtual bool OpenStream(int sid) = 0; + // The inverse of OpenStream. Begins the closing procedure, which will + // eventually result in SignalClosingProcedureComplete on the side that + // initiates it, and both SignalClosingProcedureStartedRemotely and + // SignalClosingProcedureComplete on the other side. + virtual bool ResetStream(int sid) = 0; + // Send data down this channel (will be wrapped as SCTP packets then given to + // usrsctp that will then post the network interface). + // Returns true iff successful data somewhere on the send-queue/network. + // Uses `params.ssrc` as the SCTP sid. + virtual bool SendData(int sid, + const webrtc::SendDataParams& params, + const rtc::CopyOnWriteBuffer& payload, + SendDataResult* result = nullptr) = 0; + + // Indicates when the SCTP socket is created and not blocked by congestion + // control. This changes to false when SDR_BLOCK is returned from SendData, + // and + // changes to true when SignalReadyToSendData is fired. The underlying DTLS/ + // ICE channels may be unwritable while ReadyToSendData is true, because data + // can still be queued in usrsctp. + virtual bool ReadyToSendData() = 0; + // Returns the current max message size, set with Start(). + virtual int max_message_size() const = 0; + // Returns the current negotiated max # of outbound streams. + // Will return absl::nullopt if negotiation is incomplete. + virtual absl::optional<int> max_outbound_streams() const = 0; + // Returns the current negotiated max # of inbound streams. + virtual absl::optional<int> max_inbound_streams() const = 0; + + // Helper for debugging. + virtual void set_debug_name_for_testing(const char* debug_name) = 0; +}; + +} // namespace cricket + +#endif // MEDIA_SCTP_SCTP_TRANSPORT_INTERNAL_H_ |