blob: 2800992ab60edf13b36853176203a896667782a0 [file] [log] [blame]
Harald Alvestrand00cf34c2019-12-02 09:56:02 +01001/*
2 * Copyright 2019 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
Harald Alvestrand05e4d082019-12-03 14:04:21 +010011#include "pc/data_channel_controller.h"
12
13#include <utility>
Harald Alvestrand00cf34c2019-12-02 09:56:02 +010014
15#include "pc/peer_connection.h"
16#include "pc/sctp_utils.h"
17
18namespace webrtc {
19
Harald Alvestrand05e4d082019-12-03 14:04:21 +010020bool DataChannelController::HasDataChannels() const {
21 RTC_DCHECK_RUN_ON(signaling_thread());
22 return !rtp_data_channels_.empty() || !sctp_data_channels_.empty();
23}
24
25bool DataChannelController::SendData(const cricket::SendDataParams& params,
26 const rtc::CopyOnWriteBuffer& payload,
27 cricket::SendDataResult* result) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +010028 // RTC_DCHECK_RUN_ON(signaling_thread());
29 if (data_channel_transport()) {
30 SendDataParams send_params;
31 send_params.type = ToWebrtcDataMessageType(params.type);
32 send_params.ordered = params.ordered;
33 if (params.max_rtx_count >= 0) {
34 send_params.max_rtx_count = params.max_rtx_count;
35 } else if (params.max_rtx_ms >= 0) {
36 send_params.max_rtx_ms = params.max_rtx_ms;
37 }
38
39 RTCError error = network_thread()->Invoke<RTCError>(
40 RTC_FROM_HERE, [this, params, send_params, payload] {
41 return data_channel_transport()->SendData(params.sid, send_params,
42 payload);
43 });
44
45 if (error.ok()) {
46 *result = cricket::SendDataResult::SDR_SUCCESS;
47 return true;
48 } else if (error.type() == RTCErrorType::RESOURCE_EXHAUSTED) {
49 // SCTP transport uses RESOURCE_EXHAUSTED when it's blocked.
50 // TODO(mellem): Stop using RTCError here and get rid of the mapping.
51 *result = cricket::SendDataResult::SDR_BLOCK;
52 return false;
53 }
54 *result = cricket::SendDataResult::SDR_ERROR;
55 return false;
56 } else if (rtp_data_channel()) {
57 return rtp_data_channel()->SendData(params, payload, result);
58 }
59 RTC_LOG(LS_ERROR) << "SendData called before transport is ready";
60 return false;
61}
62
Harald Alvestrand05e4d082019-12-03 14:04:21 +010063bool DataChannelController::ConnectDataChannel(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +010064 DataChannel* webrtc_data_channel) {
65 RTC_DCHECK_RUN_ON(signaling_thread());
66 if (!rtp_data_channel() && !data_channel_transport()) {
67 // Don't log an error here, because DataChannels are expected to call
68 // ConnectDataChannel in this state. It's the only way to initially tell
69 // whether or not the underlying transport is ready.
70 return false;
71 }
72 if (data_channel_transport()) {
73 SignalDataChannelTransportWritable_s.connect(webrtc_data_channel,
74 &DataChannel::OnChannelReady);
75 SignalDataChannelTransportReceivedData_s.connect(
76 webrtc_data_channel, &DataChannel::OnDataReceived);
77 SignalDataChannelTransportChannelClosing_s.connect(
78 webrtc_data_channel, &DataChannel::OnClosingProcedureStartedRemotely);
79 SignalDataChannelTransportChannelClosed_s.connect(
80 webrtc_data_channel, &DataChannel::OnClosingProcedureComplete);
81 }
82 if (rtp_data_channel()) {
83 rtp_data_channel()->SignalReadyToSendData.connect(
84 webrtc_data_channel, &DataChannel::OnChannelReady);
85 rtp_data_channel()->SignalDataReceived.connect(
86 webrtc_data_channel, &DataChannel::OnDataReceived);
87 }
88 return true;
89}
90
Harald Alvestrand05e4d082019-12-03 14:04:21 +010091void DataChannelController::DisconnectDataChannel(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +010092 DataChannel* webrtc_data_channel) {
93 RTC_DCHECK_RUN_ON(signaling_thread());
94 if (!rtp_data_channel() && !data_channel_transport()) {
95 RTC_LOG(LS_ERROR)
96 << "DisconnectDataChannel called when rtp_data_channel_ and "
97 "sctp_transport_ are NULL.";
98 return;
99 }
100 if (data_channel_transport()) {
101 SignalDataChannelTransportWritable_s.disconnect(webrtc_data_channel);
102 SignalDataChannelTransportReceivedData_s.disconnect(webrtc_data_channel);
103 SignalDataChannelTransportChannelClosing_s.disconnect(webrtc_data_channel);
104 SignalDataChannelTransportChannelClosed_s.disconnect(webrtc_data_channel);
105 }
106 if (rtp_data_channel()) {
107 rtp_data_channel()->SignalReadyToSendData.disconnect(webrtc_data_channel);
108 rtp_data_channel()->SignalDataReceived.disconnect(webrtc_data_channel);
109 }
110}
111
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100112void DataChannelController::AddSctpDataStream(int sid) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100113 if (data_channel_transport()) {
114 network_thread()->Invoke<void>(RTC_FROM_HERE, [this, sid] {
115 if (data_channel_transport()) {
116 data_channel_transport()->OpenChannel(sid);
117 }
118 });
119 }
120}
121
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100122void DataChannelController::RemoveSctpDataStream(int sid) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100123 if (data_channel_transport()) {
124 network_thread()->Invoke<void>(RTC_FROM_HERE, [this, sid] {
125 if (data_channel_transport()) {
126 data_channel_transport()->CloseChannel(sid);
127 }
128 });
129 }
130}
131
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100132bool DataChannelController::ReadyToSendData() const {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100133 RTC_DCHECK_RUN_ON(signaling_thread());
134 return (rtp_data_channel() && rtp_data_channel()->ready_to_send_data()) ||
135 (data_channel_transport() && data_channel_transport_ready_to_send_);
136}
137
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100138void DataChannelController::OnDataReceived(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100139 int channel_id,
140 DataMessageType type,
141 const rtc::CopyOnWriteBuffer& buffer) {
142 RTC_DCHECK_RUN_ON(network_thread());
143 cricket::ReceiveDataParams params;
144 params.sid = channel_id;
145 params.type = ToCricketDataMessageType(type);
146 data_channel_transport_invoker_->AsyncInvoke<void>(
147 RTC_FROM_HERE, signaling_thread(), [this, params, buffer] {
148 RTC_DCHECK_RUN_ON(signaling_thread());
149 if (!HandleOpenMessage_s(params, buffer)) {
150 SignalDataChannelTransportReceivedData_s(params, buffer);
151 }
152 });
153}
154
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100155void DataChannelController::OnChannelClosing(int channel_id) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100156 RTC_DCHECK_RUN_ON(network_thread());
157 data_channel_transport_invoker_->AsyncInvoke<void>(
158 RTC_FROM_HERE, signaling_thread(), [this, channel_id] {
159 RTC_DCHECK_RUN_ON(signaling_thread());
160 SignalDataChannelTransportChannelClosing_s(channel_id);
161 });
162}
163
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100164void DataChannelController::OnChannelClosed(int channel_id) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100165 RTC_DCHECK_RUN_ON(network_thread());
166 data_channel_transport_invoker_->AsyncInvoke<void>(
167 RTC_FROM_HERE, signaling_thread(), [this, channel_id] {
168 RTC_DCHECK_RUN_ON(signaling_thread());
169 SignalDataChannelTransportChannelClosed_s(channel_id);
170 });
171}
172
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100173void DataChannelController::OnReadyToSend() {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100174 RTC_DCHECK_RUN_ON(network_thread());
175 data_channel_transport_invoker_->AsyncInvoke<void>(
176 RTC_FROM_HERE, signaling_thread(), [this] {
177 RTC_DCHECK_RUN_ON(signaling_thread());
178 data_channel_transport_ready_to_send_ = true;
179 SignalDataChannelTransportWritable_s(
180 data_channel_transport_ready_to_send_);
181 });
182}
183
Harald Alvestrand2697ac12019-12-16 10:37:04 +0100184void DataChannelController::OnTransportClosed() {
185 RTC_DCHECK_RUN_ON(network_thread());
186 data_channel_transport_invoker_->AsyncInvoke<void>(
187 RTC_FROM_HERE, signaling_thread(), [this] {
188 RTC_DCHECK_RUN_ON(signaling_thread());
189 OnTransportChannelClosed();
190 });
191}
192
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100193void DataChannelController::SetupDataChannelTransport_n() {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100194 RTC_DCHECK_RUN_ON(network_thread());
195 data_channel_transport_invoker_ = std::make_unique<rtc::AsyncInvoker>();
196}
197
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100198void DataChannelController::TeardownDataChannelTransport_n() {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100199 RTC_DCHECK_RUN_ON(network_thread());
200 data_channel_transport_invoker_ = nullptr;
201 if (data_channel_transport()) {
202 data_channel_transport()->SetDataSink(nullptr);
203 }
204 set_data_channel_transport(nullptr);
205}
206
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100207void DataChannelController::OnTransportChanged(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100208 DataChannelTransportInterface* new_data_channel_transport) {
209 RTC_DCHECK_RUN_ON(network_thread());
210 if (data_channel_transport() &&
211 data_channel_transport() != new_data_channel_transport) {
212 // Changed which data channel transport is used for |sctp_mid_| (eg. now
213 // it's bundled).
214 data_channel_transport()->SetDataSink(nullptr);
215 set_data_channel_transport(new_data_channel_transport);
216 if (new_data_channel_transport) {
217 new_data_channel_transport->SetDataSink(this);
218
219 // There's a new data channel transport. This needs to be signaled to the
220 // |sctp_data_channels_| so that they can reopen and reconnect. This is
221 // necessary when bundling is applied.
222 data_channel_transport_invoker_->AsyncInvoke<void>(
223 RTC_FROM_HERE, signaling_thread(), [this] {
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100224 RTC_DCHECK_RUN_ON(signaling_thread());
225 for (auto channel : sctp_data_channels_) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100226 channel->OnTransportChannelCreated();
227 }
228 });
229 }
230 }
231}
232
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100233bool DataChannelController::HandleOpenMessage_s(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100234 const cricket::ReceiveDataParams& params,
235 const rtc::CopyOnWriteBuffer& buffer) {
236 if (params.type == cricket::DMT_CONTROL && IsOpenMessage(buffer)) {
237 // Received OPEN message; parse and signal that a new data channel should
238 // be created.
239 std::string label;
240 InternalDataChannelInit config;
241 config.id = params.ssrc;
242 if (!ParseDataChannelOpenMessage(buffer, &label, &config)) {
243 RTC_LOG(LS_WARNING) << "Failed to parse the OPEN message for ssrc "
244 << params.ssrc;
245 return true;
246 }
247 config.open_handshake_role = InternalDataChannelInit::kAcker;
248 OnDataChannelOpenMessage(label, config);
249 return true;
250 }
251 return false;
252}
253
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100254void DataChannelController::OnDataChannelOpenMessage(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100255 const std::string& label,
256 const InternalDataChannelInit& config) {
257 rtc::scoped_refptr<DataChannel> channel(
258 InternalCreateDataChannel(label, &config));
259 if (!channel.get()) {
260 RTC_LOG(LS_ERROR) << "Failed to create DataChannel from the OPEN message.";
261 return;
262 }
263
264 rtc::scoped_refptr<DataChannelInterface> proxy_channel =
265 DataChannelProxy::Create(signaling_thread(), channel);
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100266 pc_->Observer()->OnDataChannel(std::move(proxy_channel));
267 pc_->NoteDataAddedEvent();
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100268}
269
270rtc::scoped_refptr<DataChannel>
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100271DataChannelController::InternalCreateDataChannel(
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100272 const std::string& label,
273 const InternalDataChannelInit* config) {
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100274 RTC_DCHECK_RUN_ON(signaling_thread());
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100275 if (pc_->IsClosed()) {
276 return nullptr;
277 }
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100278 if (data_channel_type_ == cricket::DCT_NONE) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100279 RTC_LOG(LS_ERROR)
280 << "InternalCreateDataChannel: Data is not supported in this call.";
281 return nullptr;
282 }
283 InternalDataChannelInit new_config =
284 config ? (*config) : InternalDataChannelInit();
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100285 if (DataChannel::IsSctpLike(data_channel_type_)) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100286 if (new_config.id < 0) {
287 rtc::SSLRole role;
288 if ((pc_->GetSctpSslRole(&role)) &&
289 !sid_allocator_.AllocateSid(role, &new_config.id)) {
290 RTC_LOG(LS_ERROR)
291 << "No id can be allocated for the SCTP data channel.";
292 return nullptr;
293 }
294 } else if (!sid_allocator_.ReserveSid(new_config.id)) {
295 RTC_LOG(LS_ERROR) << "Failed to create a SCTP data channel "
296 "because the id is already in use or out of range.";
297 return nullptr;
298 }
299 }
300
301 rtc::scoped_refptr<DataChannel> channel(
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100302 DataChannel::Create(this, data_channel_type(), label, new_config));
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100303 if (!channel) {
304 sid_allocator_.ReleaseSid(new_config.id);
305 return nullptr;
306 }
307
308 if (channel->data_channel_type() == cricket::DCT_RTP) {
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100309 if (rtp_data_channels_.find(channel->label()) != rtp_data_channels_.end()) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100310 RTC_LOG(LS_ERROR) << "DataChannel with label " << channel->label()
311 << " already exists.";
312 return nullptr;
313 }
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100314 rtp_data_channels_[channel->label()] = channel;
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100315 } else {
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100316 RTC_DCHECK(DataChannel::IsSctpLike(data_channel_type_));
317 sctp_data_channels_.push_back(channel);
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100318 channel->SignalClosed.connect(pc_,
319 &PeerConnection::OnSctpDataChannelClosed);
320 }
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100321 SignalDataChannelCreated_(channel.get());
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100322 return channel;
323}
324
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100325void DataChannelController::AllocateSctpSids(rtc::SSLRole role) {
326 RTC_DCHECK_RUN_ON(signaling_thread());
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100327 std::vector<rtc::scoped_refptr<DataChannel>> channels_to_close;
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100328 for (const auto& channel : sctp_data_channels_) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100329 if (channel->id() < 0) {
330 int sid;
331 if (!sid_allocator_.AllocateSid(role, &sid)) {
332 RTC_LOG(LS_ERROR) << "Failed to allocate SCTP sid, closing channel.";
333 channels_to_close.push_back(channel);
334 continue;
335 }
336 channel->SetSctpSid(sid);
337 }
338 }
339 // Since closing modifies the list of channels, we have to do the actual
340 // closing outside the loop.
341 for (const auto& channel : channels_to_close) {
Harald Alvestranddfbfb462019-12-08 05:55:43 +0100342 channel->CloseAbruptlyWithDataChannelFailure("Failed to allocate SCTP SID");
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100343 }
344}
345
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100346void DataChannelController::OnSctpDataChannelClosed(DataChannel* channel) {
347 RTC_DCHECK_RUN_ON(signaling_thread());
348 for (auto it = sctp_data_channels_.begin(); it != sctp_data_channels_.end();
349 ++it) {
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100350 if (it->get() == channel) {
351 if (channel->id() >= 0) {
352 // After the closing procedure is done, it's safe to use this ID for
353 // another data channel.
354 sid_allocator_.ReleaseSid(channel->id());
355 }
356 // Since this method is triggered by a signal from the DataChannel,
357 // we can't free it directly here; we need to free it asynchronously.
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100358 sctp_data_channels_to_free_.push_back(*it);
359 sctp_data_channels_.erase(it);
Harald Alvestrand246724b2019-12-03 22:31:42 +0100360 signaling_thread()->PostTask(
361 RTC_FROM_HERE, [self = weak_factory_.GetWeakPtr()] {
362 if (self) {
363 RTC_DCHECK_RUN_ON(self->signaling_thread());
364 self->sctp_data_channels_to_free_.clear();
365 }
366 });
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100367 return;
368 }
369 }
370}
371
Harald Alvestrand05e4d082019-12-03 14:04:21 +0100372void DataChannelController::OnTransportChannelClosed() {
373 RTC_DCHECK_RUN_ON(signaling_thread());
374 // Use a temporary copy of the RTP/SCTP DataChannel list because the
375 // DataChannel may callback to us and try to modify the list.
376 std::map<std::string, rtc::scoped_refptr<DataChannel>> temp_rtp_dcs;
377 temp_rtp_dcs.swap(rtp_data_channels_);
378 for (const auto& kv : temp_rtp_dcs) {
379 kv.second->OnTransportChannelClosed();
380 }
381
382 std::vector<rtc::scoped_refptr<DataChannel>> temp_sctp_dcs;
383 temp_sctp_dcs.swap(sctp_data_channels_);
384 for (const auto& channel : temp_sctp_dcs) {
385 channel->OnTransportChannelClosed();
386 }
387}
388
389DataChannel* DataChannelController::FindDataChannelBySid(int sid) const {
390 RTC_DCHECK_RUN_ON(signaling_thread());
391 for (const auto& channel : sctp_data_channels_) {
392 if (channel->id() == sid) {
393 return channel;
394 }
395 }
396 return nullptr;
397}
398
399void DataChannelController::UpdateLocalRtpDataChannels(
400 const cricket::StreamParamsVec& streams) {
401 std::vector<std::string> existing_channels;
402
403 RTC_DCHECK_RUN_ON(signaling_thread());
404 // Find new and active data channels.
405 for (const cricket::StreamParams& params : streams) {
406 // |it->sync_label| is actually the data channel label. The reason is that
407 // we use the same naming of data channels as we do for
408 // MediaStreams and Tracks.
409 // For MediaStreams, the sync_label is the MediaStream label and the
410 // track label is the same as |streamid|.
411 const std::string& channel_label = params.first_stream_id();
412 auto data_channel_it = rtp_data_channels()->find(channel_label);
413 if (data_channel_it == rtp_data_channels()->end()) {
414 RTC_LOG(LS_ERROR) << "channel label not found";
415 continue;
416 }
417 // Set the SSRC the data channel should use for sending.
418 data_channel_it->second->SetSendSsrc(params.first_ssrc());
419 existing_channels.push_back(data_channel_it->first);
420 }
421
422 UpdateClosingRtpDataChannels(existing_channels, true);
423}
424
425void DataChannelController::UpdateRemoteRtpDataChannels(
426 const cricket::StreamParamsVec& streams) {
427 std::vector<std::string> existing_channels;
428
429 RTC_DCHECK_RUN_ON(signaling_thread());
430 // Find new and active data channels.
431 for (const cricket::StreamParams& params : streams) {
432 // The data channel label is either the mslabel or the SSRC if the mslabel
433 // does not exist. Ex a=ssrc:444330170 mslabel:test1.
434 std::string label = params.first_stream_id().empty()
435 ? rtc::ToString(params.first_ssrc())
436 : params.first_stream_id();
437 auto data_channel_it = rtp_data_channels()->find(label);
438 if (data_channel_it == rtp_data_channels()->end()) {
439 // This is a new data channel.
440 CreateRemoteRtpDataChannel(label, params.first_ssrc());
441 } else {
442 data_channel_it->second->SetReceiveSsrc(params.first_ssrc());
443 }
444 existing_channels.push_back(label);
445 }
446
447 UpdateClosingRtpDataChannels(existing_channels, false);
448}
449
450void DataChannelController::UpdateClosingRtpDataChannels(
451 const std::vector<std::string>& active_channels,
452 bool is_local_update) {
453 auto it = rtp_data_channels_.begin();
454 while (it != rtp_data_channels_.end()) {
455 DataChannel* data_channel = it->second;
456 if (absl::c_linear_search(active_channels, data_channel->label())) {
457 ++it;
458 continue;
459 }
460
461 if (is_local_update) {
462 data_channel->SetSendSsrc(0);
463 } else {
464 data_channel->RemotePeerRequestClose();
465 }
466
467 if (data_channel->state() == DataChannel::kClosed) {
468 rtp_data_channels_.erase(it);
469 it = rtp_data_channels_.begin();
470 } else {
471 ++it;
472 }
473 }
474}
475
476void DataChannelController::CreateRemoteRtpDataChannel(const std::string& label,
477 uint32_t remote_ssrc) {
478 rtc::scoped_refptr<DataChannel> channel(
479 InternalCreateDataChannel(label, nullptr));
480 if (!channel.get()) {
481 RTC_LOG(LS_WARNING) << "Remote peer requested a DataChannel but"
482 "CreateDataChannel failed.";
483 return;
484 }
485 channel->SetReceiveSsrc(remote_ssrc);
486 rtc::scoped_refptr<DataChannelInterface> proxy_channel =
487 DataChannelProxy::Create(signaling_thread(), channel);
488 pc_->Observer()->OnDataChannel(std::move(proxy_channel));
489}
490
491rtc::Thread* DataChannelController::network_thread() const {
492 return pc_->network_thread();
493}
494rtc::Thread* DataChannelController::signaling_thread() const {
495 return pc_->signaling_thread();
496}
497
Harald Alvestrand00cf34c2019-12-02 09:56:02 +0100498} // namespace webrtc