Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(323)

Side by Side Diff: webrtc/libjingle/session/tunnel/pseudotcpchannel.cc

Issue 1175243003: Remove webrtc/libjingle/{examples,session}. (Closed) Base URL: https://chromium.googlesource.com/external/webrtc.git@master
Patch Set: Created 5 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * libjingle
3 * Copyright 2004--2006, Google Inc.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation
12 * and/or other materials provided with the distribution.
13 * 3. The name of the author may not be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 #include <algorithm>
29 #include <string>
30
31 #include "pseudotcpchannel.h"
32 #include "webrtc/p2p/base/candidate.h"
33 #include "webrtc/p2p/base/transportchannel.h"
34 #include "webrtc/base/basictypes.h"
35 #include "webrtc/base/common.h"
36 #include "webrtc/base/logging.h"
37 #include "webrtc/base/scoped_ptr.h"
38 #include "webrtc/base/stringutils.h"
39
40 using namespace rtc;
41
42 namespace cricket {
43
44 extern const rtc::ConstantLabel SESSION_STATES[];
45
46 // MSG_WK_* - worker thread messages
47 // MSG_ST_* - stream thread messages
48 // MSG_SI_* - signal thread messages
49
50 enum {
51 MSG_WK_CLOCK = 1,
52 MSG_WK_PURGE,
53 MSG_ST_EVENT,
54 MSG_SI_DESTROYCHANNEL,
55 MSG_SI_DESTROY,
56 };
57
58 struct EventData : public MessageData {
59 int event, error;
60 EventData(int ev, int err = 0) : event(ev), error(err) { }
61 };
62
63 ///////////////////////////////////////////////////////////////////////////////
64 // PseudoTcpChannel::InternalStream
65 ///////////////////////////////////////////////////////////////////////////////
66
67 class PseudoTcpChannel::InternalStream : public StreamInterface {
68 public:
69 InternalStream(PseudoTcpChannel* parent);
70 virtual ~InternalStream();
71
72 virtual StreamState GetState() const;
73 virtual StreamResult Read(void* buffer, size_t buffer_len,
74 size_t* read, int* error);
75 virtual StreamResult Write(const void* data, size_t data_len,
76 size_t* written, int* error);
77 virtual void Close();
78
79 private:
80 // parent_ is accessed and modified exclusively on the event thread, to
81 // avoid thread contention. This means that the PseudoTcpChannel cannot go
82 // away until after it receives a Close() from TunnelStream.
83 PseudoTcpChannel* parent_;
84 };
85
86 ///////////////////////////////////////////////////////////////////////////////
87 // PseudoTcpChannel
88 // Member object lifetime summaries:
89 // session_ - passed in constructor, cleared when channel_ goes away.
90 // channel_ - created in Connect, destroyed when session_ or tcp_ goes away.
91 // tcp_ - created in Connect, destroyed when channel_ goes away, or connection
92 // closes.
93 // worker_thread_ - created when channel_ is created, purged when channel_ is
94 // destroyed.
95 // stream_ - created in GetStream, destroyed by owner at arbitrary time.
96 // this - created in constructor, destroyed when worker_thread_ and stream_
97 // are both gone.
98 ///////////////////////////////////////////////////////////////////////////////
99
100 //
101 // Signal thread methods
102 //
103
104 PseudoTcpChannel::PseudoTcpChannel(Thread* stream_thread, Session* session)
105 : signal_thread_(session->session_manager()->signaling_thread()),
106 worker_thread_(NULL),
107 stream_thread_(stream_thread),
108 session_(session), channel_(NULL), tcp_(NULL), stream_(NULL),
109 stream_readable_(false), pending_read_event_(false),
110 ready_to_connect_(false) {
111 ASSERT(signal_thread_->IsCurrent());
112 ASSERT(NULL != session_);
113 }
114
115 PseudoTcpChannel::~PseudoTcpChannel() {
116 ASSERT(signal_thread_->IsCurrent());
117 ASSERT(worker_thread_ == NULL);
118 ASSERT(session_ == NULL);
119 ASSERT(channel_ == NULL);
120 ASSERT(stream_ == NULL);
121 ASSERT(tcp_ == NULL);
122 }
123
124 bool PseudoTcpChannel::Connect(const std::string& content_name,
125 const std::string& channel_name,
126 int component) {
127 ASSERT(signal_thread_->IsCurrent());
128 CritScope lock(&cs_);
129
130 if (channel_)
131 return false;
132
133 ASSERT(session_ != NULL);
134 worker_thread_ = session_->session_manager()->worker_thread();
135 content_name_ = content_name;
136 channel_ = session_->CreateChannel(
137 content_name, channel_name, component);
138 channel_name_ = channel_name;
139 channel_->SetOption(Socket::OPT_DONTFRAGMENT, 1);
140
141 channel_->SignalDestroyed.connect(this,
142 &PseudoTcpChannel::OnChannelDestroyed);
143 channel_->SignalWritableState.connect(this,
144 &PseudoTcpChannel::OnChannelWritableState);
145 channel_->SignalReadPacket.connect(this,
146 &PseudoTcpChannel::OnChannelRead);
147 channel_->SignalRouteChange.connect(this,
148 &PseudoTcpChannel::OnChannelConnectionChanged);
149
150 ASSERT(tcp_ == NULL);
151 tcp_ = new PseudoTcp(this, 0);
152 if (session_->initiator()) {
153 // Since we may try several protocols and network adapters that won't work,
154 // waiting until we get our first writable notification before initiating
155 // TCP negotiation.
156 ready_to_connect_ = true;
157 }
158
159 return true;
160 }
161
162 StreamInterface* PseudoTcpChannel::GetStream() {
163 ASSERT(signal_thread_->IsCurrent());
164 CritScope lock(&cs_);
165 ASSERT(NULL != session_);
166 if (!stream_)
167 stream_ = new PseudoTcpChannel::InternalStream(this);
168 //TODO("should we disallow creation of new stream at some point?");
169 return stream_;
170 }
171
172 void PseudoTcpChannel::OnChannelDestroyed(TransportChannel* channel) {
173 LOG_F(LS_INFO) << "(" << channel->component() << ")";
174 ASSERT(signal_thread_->IsCurrent());
175 CritScope lock(&cs_);
176 ASSERT(channel == channel_);
177 signal_thread_->Clear(this, MSG_SI_DESTROYCHANNEL);
178 // When MSG_WK_PURGE is received, we know there will be no more messages from
179 // the worker thread.
180 worker_thread_->Clear(this, MSG_WK_CLOCK);
181 worker_thread_->Post(this, MSG_WK_PURGE);
182 session_ = NULL;
183 channel_ = NULL;
184 if ((stream_ != NULL)
185 && ((tcp_ == NULL) || (tcp_->State() != PseudoTcp::TCP_CLOSED)))
186 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, 0));
187 if (tcp_) {
188 tcp_->Close(true);
189 AdjustClock();
190 }
191 SignalChannelClosed(this);
192 }
193
194 void PseudoTcpChannel::OnSessionTerminate(Session* session) {
195 // When the session terminates before we even connected
196 CritScope lock(&cs_);
197 if (session_ != NULL && channel_ == NULL) {
198 ASSERT(session == session_);
199 ASSERT(worker_thread_ == NULL);
200 ASSERT(tcp_ == NULL);
201 LOG(LS_INFO) << "Destroying unconnected PseudoTcpChannel";
202 session_ = NULL;
203 if (stream_ != NULL)
204 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, -1));
205 }
206
207 // Even though session_ is being destroyed, we mustn't clear the pointer,
208 // since we'll need it to tear down channel_.
209 //
210 // TODO: Is it always the case that if channel_ != NULL then we'll get
211 // a channel-destroyed notification?
212 }
213
214 void PseudoTcpChannel::GetOption(PseudoTcp::Option opt, int* value) {
215 ASSERT(signal_thread_->IsCurrent());
216 CritScope lock(&cs_);
217 ASSERT(tcp_ != NULL);
218 tcp_->GetOption(opt, value);
219 }
220
221 void PseudoTcpChannel::SetOption(PseudoTcp::Option opt, int value) {
222 ASSERT(signal_thread_->IsCurrent());
223 CritScope lock(&cs_);
224 ASSERT(tcp_ != NULL);
225 tcp_->SetOption(opt, value);
226 }
227
228 //
229 // Stream thread methods
230 //
231
232 StreamState PseudoTcpChannel::GetState() const {
233 ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
234 CritScope lock(&cs_);
235 if (!session_)
236 return SS_CLOSED;
237 if (!tcp_)
238 return SS_OPENING;
239 switch (tcp_->State()) {
240 case PseudoTcp::TCP_LISTEN:
241 case PseudoTcp::TCP_SYN_SENT:
242 case PseudoTcp::TCP_SYN_RECEIVED:
243 return SS_OPENING;
244 case PseudoTcp::TCP_ESTABLISHED:
245 return SS_OPEN;
246 case PseudoTcp::TCP_CLOSED:
247 default:
248 return SS_CLOSED;
249 }
250 }
251
252 StreamResult PseudoTcpChannel::Read(void* buffer, size_t buffer_len,
253 size_t* read, int* error) {
254 ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
255 CritScope lock(&cs_);
256 if (!tcp_)
257 return SR_BLOCK;
258
259 stream_readable_ = false;
260 int result = tcp_->Recv(static_cast<char*>(buffer), buffer_len);
261 //LOG_F(LS_VERBOSE) << "Recv returned: " << result;
262 if (result > 0) {
263 if (read)
264 *read = result;
265 // PseudoTcp doesn't currently support repeated Readable signals. Simulate
266 // them here.
267 stream_readable_ = true;
268 if (!pending_read_event_) {
269 pending_read_event_ = true;
270 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ), true);
271 }
272 return SR_SUCCESS;
273 } else if (IsBlockingError(tcp_->GetError())) {
274 return SR_BLOCK;
275 } else {
276 if (error)
277 *error = tcp_->GetError();
278 return SR_ERROR;
279 }
280 // This spot is never reached.
281 }
282
283 StreamResult PseudoTcpChannel::Write(const void* data, size_t data_len,
284 size_t* written, int* error) {
285 ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
286 CritScope lock(&cs_);
287 if (!tcp_)
288 return SR_BLOCK;
289 int result = tcp_->Send(static_cast<const char*>(data), data_len);
290 //LOG_F(LS_VERBOSE) << "Send returned: " << result;
291 if (result > 0) {
292 if (written)
293 *written = result;
294 return SR_SUCCESS;
295 } else if (IsBlockingError(tcp_->GetError())) {
296 return SR_BLOCK;
297 } else {
298 if (error)
299 *error = tcp_->GetError();
300 return SR_ERROR;
301 }
302 // This spot is never reached.
303 }
304
305 void PseudoTcpChannel::Close() {
306 ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
307 CritScope lock(&cs_);
308 stream_ = NULL;
309 // Clear out any pending event notifications
310 stream_thread_->Clear(this, MSG_ST_EVENT);
311 if (tcp_) {
312 tcp_->Close(false);
313 AdjustClock();
314 } else {
315 CheckDestroy();
316 }
317 }
318
319 //
320 // Worker thread methods
321 //
322
323 void PseudoTcpChannel::OnChannelWritableState(TransportChannel* channel) {
324 LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
325 ASSERT(worker_thread_->IsCurrent());
326 CritScope lock(&cs_);
327 if (!channel_) {
328 LOG_F(LS_WARNING) << "NULL channel";
329 return;
330 }
331 ASSERT(channel == channel_);
332 if (!tcp_) {
333 LOG_F(LS_WARNING) << "NULL tcp";
334 return;
335 }
336 if (!ready_to_connect_ || !channel->writable())
337 return;
338
339 ready_to_connect_ = false;
340 tcp_->Connect();
341 AdjustClock();
342 }
343
344 void PseudoTcpChannel::OnChannelRead(TransportChannel* channel,
345 const char* data, size_t size,
346 const rtc::PacketTime& packet_time,
347 int flags) {
348 //LOG_F(LS_VERBOSE) << "(" << size << ")";
349 ASSERT(worker_thread_->IsCurrent());
350 CritScope lock(&cs_);
351 if (!channel_) {
352 LOG_F(LS_WARNING) << "NULL channel";
353 return;
354 }
355 ASSERT(channel == channel_);
356 if (!tcp_) {
357 LOG_F(LS_WARNING) << "NULL tcp";
358 return;
359 }
360 tcp_->NotifyPacket(data, size);
361 AdjustClock();
362 }
363
364 void PseudoTcpChannel::OnChannelConnectionChanged(TransportChannel* channel,
365 const Candidate& candidate) {
366 LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
367 ASSERT(worker_thread_->IsCurrent());
368 CritScope lock(&cs_);
369 if (!channel_) {
370 LOG_F(LS_WARNING) << "NULL channel";
371 return;
372 }
373 ASSERT(channel == channel_);
374 if (!tcp_) {
375 LOG_F(LS_WARNING) << "NULL tcp";
376 return;
377 }
378
379 uint16 mtu = 1280; // safe default
380 int family = candidate.address().family();
381 Socket* socket =
382 worker_thread_->socketserver()->CreateAsyncSocket(family, SOCK_DGRAM);
383 rtc::scoped_ptr<Socket> mtu_socket(socket);
384 if (socket == NULL) {
385 LOG_F(LS_WARNING) << "Couldn't create socket while estimating MTU.";
386 } else {
387 if (mtu_socket->Connect(candidate.address()) < 0 ||
388 mtu_socket->EstimateMTU(&mtu) < 0) {
389 LOG_F(LS_WARNING) << "Failed to estimate MTU, error="
390 << mtu_socket->GetError();
391 }
392 }
393
394 LOG_F(LS_VERBOSE) << "Using MTU of " << mtu << " bytes";
395 tcp_->NotifyMTU(mtu);
396 AdjustClock();
397 }
398
399 void PseudoTcpChannel::OnTcpOpen(PseudoTcp* tcp) {
400 LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
401 ASSERT(cs_.CurrentThreadIsOwner());
402 ASSERT(worker_thread_->IsCurrent());
403 ASSERT(tcp == tcp_);
404 if (stream_) {
405 stream_readable_ = true;
406 pending_read_event_ = true;
407 stream_thread_->Post(this, MSG_ST_EVENT,
408 new EventData(SE_OPEN | SE_READ | SE_WRITE));
409 }
410 }
411
412 void PseudoTcpChannel::OnTcpReadable(PseudoTcp* tcp) {
413 //LOG_F(LS_VERBOSE);
414 ASSERT(cs_.CurrentThreadIsOwner());
415 ASSERT(worker_thread_->IsCurrent());
416 ASSERT(tcp == tcp_);
417 if (stream_) {
418 stream_readable_ = true;
419 if (!pending_read_event_) {
420 pending_read_event_ = true;
421 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ));
422 }
423 }
424 }
425
426 void PseudoTcpChannel::OnTcpWriteable(PseudoTcp* tcp) {
427 //LOG_F(LS_VERBOSE);
428 ASSERT(cs_.CurrentThreadIsOwner());
429 ASSERT(worker_thread_->IsCurrent());
430 ASSERT(tcp == tcp_);
431 if (stream_)
432 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_WRITE));
433 }
434
435 void PseudoTcpChannel::OnTcpClosed(PseudoTcp* tcp, uint32 nError) {
436 LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
437 ASSERT(cs_.CurrentThreadIsOwner());
438 ASSERT(worker_thread_->IsCurrent());
439 ASSERT(tcp == tcp_);
440 if (stream_)
441 stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, nError));
442 }
443
444 //
445 // Multi-thread methods
446 //
447
448 void PseudoTcpChannel::OnMessage(Message* pmsg) {
449 if (pmsg->message_id == MSG_WK_CLOCK) {
450
451 ASSERT(worker_thread_->IsCurrent());
452 //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_WK_CLOCK)";
453 CritScope lock(&cs_);
454 if (tcp_) {
455 tcp_->NotifyClock(PseudoTcp::Now());
456 AdjustClock(false);
457 }
458
459 } else if (pmsg->message_id == MSG_WK_PURGE) {
460
461 ASSERT(worker_thread_->IsCurrent());
462 LOG_F(LS_INFO) << "(MSG_WK_PURGE)";
463 // At this point, we know there are no additional worker thread messages.
464 CritScope lock(&cs_);
465 ASSERT(NULL == session_);
466 ASSERT(NULL == channel_);
467 worker_thread_ = NULL;
468 CheckDestroy();
469
470 } else if (pmsg->message_id == MSG_ST_EVENT) {
471
472 ASSERT(stream_thread_->IsCurrent());
473 //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_ST_EVENT, "
474 // << data->event << ", " << data->error << ")";
475 ASSERT(stream_ != NULL);
476 EventData* data = static_cast<EventData*>(pmsg->pdata);
477 if (data->event & SE_READ) {
478 CritScope lock(&cs_);
479 pending_read_event_ = false;
480 }
481 stream_->SignalEvent(stream_, data->event, data->error);
482 delete data;
483
484 } else if (pmsg->message_id == MSG_SI_DESTROYCHANNEL) {
485
486 ASSERT(signal_thread_->IsCurrent());
487 LOG_F(LS_INFO) << "(MSG_SI_DESTROYCHANNEL)";
488 ASSERT(session_ != NULL);
489 ASSERT(channel_ != NULL);
490 session_->DestroyChannel(content_name_, channel_->component());
491
492 } else if (pmsg->message_id == MSG_SI_DESTROY) {
493
494 ASSERT(signal_thread_->IsCurrent());
495 LOG_F(LS_INFO) << "(MSG_SI_DESTROY)";
496 // The message queue is empty, so it is safe to destroy ourselves.
497 delete this;
498
499 } else {
500 ASSERT(false);
501 }
502 }
503
504 IPseudoTcpNotify::WriteResult PseudoTcpChannel::TcpWritePacket(
505 PseudoTcp* tcp, const char* buffer, size_t len) {
506 ASSERT(cs_.CurrentThreadIsOwner());
507 ASSERT(tcp == tcp_);
508 ASSERT(NULL != channel_);
509 rtc::PacketOptions packet_options;
510 int sent = channel_->SendPacket(buffer, len, packet_options);
511 if (sent > 0) {
512 //LOG_F(LS_VERBOSE) << "(" << sent << ") Sent";
513 return IPseudoTcpNotify::WR_SUCCESS;
514 } else if (IsBlockingError(channel_->GetError())) {
515 LOG_F(LS_VERBOSE) << "Blocking";
516 return IPseudoTcpNotify::WR_SUCCESS;
517 } else if (channel_->GetError() == EMSGSIZE) {
518 LOG_F(LS_ERROR) << "EMSGSIZE";
519 return IPseudoTcpNotify::WR_TOO_LARGE;
520 } else {
521 PLOG(LS_ERROR, channel_->GetError()) << "PseudoTcpChannel::TcpWritePacket";
522 ASSERT(false);
523 return IPseudoTcpNotify::WR_FAIL;
524 }
525 }
526
527 void PseudoTcpChannel::AdjustClock(bool clear) {
528 ASSERT(cs_.CurrentThreadIsOwner());
529 ASSERT(NULL != tcp_);
530
531 long timeout = 0;
532 if (tcp_->GetNextClock(PseudoTcp::Now(), timeout)) {
533 ASSERT(NULL != channel_);
534 // Reset the next clock, by clearing the old and setting a new one.
535 if (clear)
536 worker_thread_->Clear(this, MSG_WK_CLOCK);
537 worker_thread_->PostDelayed(std::max(timeout, 0L), this, MSG_WK_CLOCK);
538 return;
539 }
540
541 delete tcp_;
542 tcp_ = NULL;
543 ready_to_connect_ = false;
544
545 if (channel_) {
546 // If TCP has failed, no need for channel_ anymore
547 signal_thread_->Post(this, MSG_SI_DESTROYCHANNEL);
548 }
549 }
550
551 void PseudoTcpChannel::CheckDestroy() {
552 ASSERT(cs_.CurrentThreadIsOwner());
553 if ((worker_thread_ != NULL) || (stream_ != NULL))
554 return;
555 signal_thread_->Post(this, MSG_SI_DESTROY);
556 }
557
558 ///////////////////////////////////////////////////////////////////////////////
559 // PseudoTcpChannel::InternalStream
560 ///////////////////////////////////////////////////////////////////////////////
561
562 PseudoTcpChannel::InternalStream::InternalStream(PseudoTcpChannel* parent)
563 : parent_(parent) {
564 }
565
566 PseudoTcpChannel::InternalStream::~InternalStream() {
567 Close();
568 }
569
570 StreamState PseudoTcpChannel::InternalStream::GetState() const {
571 if (!parent_)
572 return SS_CLOSED;
573 return parent_->GetState();
574 }
575
576 StreamResult PseudoTcpChannel::InternalStream::Read(
577 void* buffer, size_t buffer_len, size_t* read, int* error) {
578 if (!parent_) {
579 if (error)
580 *error = ENOTCONN;
581 return SR_ERROR;
582 }
583 return parent_->Read(buffer, buffer_len, read, error);
584 }
585
586 StreamResult PseudoTcpChannel::InternalStream::Write(
587 const void* data, size_t data_len, size_t* written, int* error) {
588 if (!parent_) {
589 if (error)
590 *error = ENOTCONN;
591 return SR_ERROR;
592 }
593 return parent_->Write(data, data_len, written, error);
594 }
595
596 void PseudoTcpChannel::InternalStream::Close() {
597 if (!parent_)
598 return;
599 parent_->Close();
600 parent_ = NULL;
601 }
602
603 ///////////////////////////////////////////////////////////////////////////////
604
605 } // namespace cricket
OLDNEW
« no previous file with comments | « webrtc/libjingle/session/tunnel/pseudotcpchannel.h ('k') | webrtc/libjingle/session/tunnel/securetunnelsessionclient.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698