/*
* 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 <optional>
#include <utility>
#include <vector>
#include "absl/strings/string_view.h"
#include "api/array_view.h"
#include "api/data_channel_interface.h"
#include "api/environment/environment.h"
#include "api/priority.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/network/received_packet.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 DataMessageType::kControl:
return WebrtcPPID::kDCEP;
case DataMessageType::kText:
return size > 0 ? WebrtcPPID::kString : WebrtcPPID::kStringEmpty;
case DataMessageType::kBinary:
return size > 0 ? WebrtcPPID::kBinary : WebrtcPPID::kBinaryEmpty;
}
}
std::optional<DataMessageType> ToDataMessageType(dcsctp::PPID ppid) {
switch (
static_cast<WebrtcPPID>(ppid.value())) {
case WebrtcPPID::kDCEP:
return DataMessageType::kControl;
case WebrtcPPID::kString:
case WebrtcPPID::kStringPartial:
case WebrtcPPID::kStringEmpty:
return DataMessageType::kText;
case WebrtcPPID::kBinary:
case WebrtcPPID::kBinaryPartial:
case WebrtcPPID::kBinaryEmpty:
return DataMessageType::kBinary;
}
return std::nullopt;
}
std::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 std::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(
const Environment& env,
rtc::Thread* network_thread,
rtc::PacketTransportInternal* transport)
: DcSctpTransport(env,
network_thread,
transport,
std::make_unique<dcsctp::DcSctpSocketFactory>()) {}
DcSctpTransport::DcSctpTransport(
const Environment& env,
rtc::Thread* network_thread,
rtc::PacketTransportInternal* transport,
std::unique_ptr<dcsctp::DcSctpSocketFactory> socket_factory)
: network_thread_(network_thread),
transport_(transport),
env_(env),
random_(env_.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 = std::nullopt;
options.max_init_retransmits = std::nullopt;
options.per_stream_send_queue_limit =
DataChannelInterface::MaxSendQueueSize();
// This is just set to avoid denial-of-service. Practically unlimited.
options.max_send_buffer_size = std::numeric_limits<size_t>::max();
options.enable_message_interleaving =
env_.field_trials().IsEnabled(
"WebRTC-DataChannelMessageInterleaving");
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();
for (
const auto& [sid, stream_state] : stream_states_) {
socket_->SetStreamPriority(sid, stream_state.priority);
}
return true;
}
bool DcSctpTransport::OpenStream(
int sid, PriorityValue priority) {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DLOG(LS_INFO) << debug_name_ <<
"->OpenStream(" << sid <<
", "
<< priority.value() <<
").";
StreamState stream_state;
stream_state.priority = dcsctp::StreamPriority(priority.value());
stream_states_.insert_or_assign(dcsctp::StreamID(
static_cast<uint16_t>(sid)),
stream_state);
if (socket_) {
socket_->SetStreamPriority(dcsctp::StreamID(sid),
dcsctp::StreamPriority(priority.value()));
}
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;
}
RTCError DcSctpTransport::SendData(
int sid,
const SendDataParams& params,
const rtc::CopyOnWriteBuffer& payload) {
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.";
return RTCError(RTCErrorType::INVALID_STATE);
}
// 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;
return RTCError(RTCErrorType::INVALID_STATE);
}
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;
return RTCError(RTCErrorType::INVALID_STATE);
}
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;
return RTCError(RTCErrorType::INVALID_RANGE);
}
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;
}
dcsctp::SendStatus error = socket_->Send(std::move(message), send_options);
switch (error) {
case dcsctp::SendStatus::kSuccess:
return RTCError::OK();
case dcsctp::SendStatus::kErrorResourceExhaustion:
ready_to_send_data_ =
false;
return RTCError(RTCErrorType::RESOURCE_EXHAUSTED);
default:
absl::string_view message = dcsctp::ToString(error);
RTC_LOG(LS_ERROR) << debug_name_
<<
"->SendData(...): send() failed with error "
<< message <<
".";
return RTCError(RTCErrorType::NETWORK_ERROR, message);
}
}
bool DcSctpTransport::ReadyToSendData() {
RTC_DCHECK_RUN_ON(network_thread_);
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;
}
std::optional<
int> DcSctpTransport::max_outbound_streams()
const {
if (!socket_)
return std::nullopt;
return socket_->options().announced_maximum_outgoing_streams;
}
std::optional<
int> DcSctpTransport::max_inbound_streams()
const {
if (!socket_)
return std::nullopt;
return socket_->options().announced_maximum_incoming_streams;
}
size_t DcSctpTransport::buffered_amount(
int sid)
const {
if (!socket_)
return 0;
return socket_->buffered_amount(dcsctp::StreamID(sid));
}
size_t DcSctpTransport::buffered_amount_low_threshold(
int sid)
const {
if (!socket_)
return 0;
return socket_->buffered_amount_low_threshold(dcsctp::StreamID(sid));
}
void DcSctpTransport::SetBufferedAmountLowThreshold(
int sid, size_t bytes) {
if (!socket_)
return;
socket_->SetBufferedAmountLowThreshold(dcsctp::StreamID(sid), bytes);
}
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(
TaskQueueBase::DelayPrecision precision) {
return task_queue_timeout_factory_.CreateTimeout(precision);
}
dcsctp::TimeMs DcSctpTransport::TimeMillis() {
return dcsctp::TimeMs(env_.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::OnBufferedAmountLow(dcsctp::StreamID stream_id) {
RTC_DCHECK_RUN_ON(network_thread_);
if (data_channel_sink_) {
data_channel_sink_->OnBufferedAmountLow(*stream_id);
}
}
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() <<
").";
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.";
return;
}
receive_buffer_.Clear();
if (!IsEmptyPPID(message.ppid()))
receive_buffer_.AppendData(message.payload().data(),
message.payload().size());
if (data_channel_sink_) {
data_channel_sink_->OnDataReceived(message.stream_id().value(), *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_DCHECK_RUN_ON(network_thread_);
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_->RegisterReceivedPacketCallback(
this, [&](rtc::PacketTransportInternal* transport,
const rtc::ReceivedPacket& packet) {
OnTransportReadPacket(transport, packet);
});
transport_->SetOnCloseCallback([
this]() {
RTC_DCHECK_RUN_ON(network_thread_);
RTC_DLOG(LS_VERBOSE) << debug_name_ <<
"->OnTransportClosed().";
if (data_channel_sink_) {
data_channel_sink_->OnTransportClosed({});
}
});
}
void DcSctpTransport::DisconnectTransportSignals() {
RTC_DCHECK_RUN_ON(network_thread_);
if (!transport_) {
return;
}
transport_->SignalWritableState.disconnect(
this);
transport_->DeregisterReceivedPacketCallback(
this);
transport_->SetOnCloseCallback(nullptr);
}
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 rtc::ReceivedPacket& packet) {
RTC_DCHECK_RUN_ON(network_thread_);
if (packet.decryption_info() != rtc::ReceivedPacket::kDtlsDecrypted) {
// We are only interested in SCTP packets.
return;
}
RTC_DLOG(LS_VERBOSE) << debug_name_ <<
"->OnTransportReadPacket(), length="
<< packet.payload().size();
if (socket_) {
socket_->ReceivePacket(packet.payload());
}
}
void DcSctpTransport::MaybeConnectSocket() {
if (transport_ && transport_->writable() && socket_ &&
socket_->state() == dcsctp::SocketState::kClosed) {
socket_->Connect();
}
}
}
// namespace webrtc