blob: 8c391a83687560f58fc5fca93fb0b110699bc282 [file] [log] [blame]
henrike@webrtc.org28e20752013-07-10 00:45:36 +00001/*
Donald E Curtisa8736442015-08-05 15:48:13 -07002 * Copyright 2012 The WebRTC Project Authors. All rights reserved.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00003 *
Donald E Curtisa8736442015-08-05 15:48:13 -07004 * 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.
henrike@webrtc.org28e20752013-07-10 00:45:36 +00009 */
10
Donald E Curtisa8736442015-08-05 15:48:13 -070011#include "webrtc/examples/peerconnection/client/peer_connection_client.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000012
Henrik Kjellandera80c16a2017-07-01 16:48:15 +020013#include "webrtc/base/checks.h"
14#include "webrtc/base/logging.h"
15#include "webrtc/base/nethelpers.h"
16#include "webrtc/base/stringutils.h"
17#include "webrtc/base/thread.h"
nissebc8feda2017-06-29 06:21:20 -070018#include "webrtc/examples/peerconnection/client/defaults.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000019
20#ifdef WIN32
Henrik Kjellandera80c16a2017-07-01 16:48:15 +020021#include "webrtc/base/win32socketserver.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000022#endif
23
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000024using rtc::sprintfn;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000025
26namespace {
27
28// This is our magical hangup signal.
29const char kByeMessage[] = "BYE";
30// Delay between server connection retries, in milliseconds
31const int kReconnectDelay = 2000;
32
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000033rtc::AsyncSocket* CreateClientSocket(int family) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000034#ifdef WIN32
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000035 rtc::Win32Socket* sock = new rtc::Win32Socket();
henrike@webrtc.org28e20752013-07-10 00:45:36 +000036 sock->CreateT(family, SOCK_STREAM);
37 return sock;
Thiago Farina91543732015-04-20 13:14:36 +020038#elif defined(WEBRTC_POSIX)
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000039 rtc::Thread* thread = rtc::Thread::Current();
nisseede5da42017-01-12 05:15:36 -080040 RTC_DCHECK(thread != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000041 return thread->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
42#else
43#error Platform not supported.
44#endif
45}
46
jbauch70625e52015-12-09 14:18:14 -080047} // namespace
henrike@webrtc.org28e20752013-07-10 00:45:36 +000048
49PeerConnectionClient::PeerConnectionClient()
50 : callback_(NULL),
51 resolver_(NULL),
52 state_(NOT_CONNECTED),
53 my_id_(-1) {
54}
55
56PeerConnectionClient::~PeerConnectionClient() {
57}
58
59void PeerConnectionClient::InitSocketSignals() {
nisseede5da42017-01-12 05:15:36 -080060 RTC_DCHECK(control_socket_.get() != NULL);
61 RTC_DCHECK(hanging_get_.get() != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000062 control_socket_->SignalCloseEvent.connect(this,
63 &PeerConnectionClient::OnClose);
64 hanging_get_->SignalCloseEvent.connect(this,
65 &PeerConnectionClient::OnClose);
66 control_socket_->SignalConnectEvent.connect(this,
67 &PeerConnectionClient::OnConnect);
68 hanging_get_->SignalConnectEvent.connect(this,
69 &PeerConnectionClient::OnHangingGetConnect);
70 control_socket_->SignalReadEvent.connect(this,
71 &PeerConnectionClient::OnRead);
72 hanging_get_->SignalReadEvent.connect(this,
73 &PeerConnectionClient::OnHangingGetRead);
74}
75
76int PeerConnectionClient::id() const {
77 return my_id_;
78}
79
80bool PeerConnectionClient::is_connected() const {
81 return my_id_ != -1;
82}
83
84const Peers& PeerConnectionClient::peers() const {
85 return peers_;
86}
87
88void PeerConnectionClient::RegisterObserver(
89 PeerConnectionClientObserver* callback) {
nisseede5da42017-01-12 05:15:36 -080090 RTC_DCHECK(!callback_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000091 callback_ = callback;
92}
93
94void PeerConnectionClient::Connect(const std::string& server, int port,
95 const std::string& client_name) {
nisseede5da42017-01-12 05:15:36 -080096 RTC_DCHECK(!server.empty());
97 RTC_DCHECK(!client_name.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +000098
99 if (state_ != NOT_CONNECTED) {
100 LOG(WARNING)
101 << "The client must not be connected before you can call Connect()";
102 callback_->OnServerConnectionFailure();
103 return;
104 }
105
106 if (server.empty() || client_name.empty()) {
107 callback_->OnServerConnectionFailure();
108 return;
109 }
110
111 if (port <= 0)
112 port = kDefaultServerPort;
113
114 server_address_.SetIP(server);
115 server_address_.SetPort(port);
116 client_name_ = client_name;
117
tfarina20a34612015-11-02 16:20:22 -0800118 if (server_address_.IsUnresolvedIP()) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000119 state_ = RESOLVING;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000120 resolver_ = new rtc::AsyncResolver();
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000121 resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult);
122 resolver_->Start(server_address_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000123 } else {
124 DoConnect();
125 }
126}
127
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000128void PeerConnectionClient::OnResolveResult(
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000129 rtc::AsyncResolverInterface* resolver) {
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000130 if (resolver_->GetError() != 0) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000131 callback_->OnServerConnectionFailure();
132 resolver_->Destroy(false);
133 resolver_ = NULL;
134 state_ = NOT_CONNECTED;
135 } else {
136 server_address_ = resolver_->address();
137 DoConnect();
138 }
139}
140
141void PeerConnectionClient::DoConnect() {
142 control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
143 hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
144 InitSocketSignals();
145 char buffer[1024];
146 sprintfn(buffer, sizeof(buffer),
147 "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str());
148 onconnect_data_ = buffer;
149
150 bool ret = ConnectControlSocket();
151 if (ret)
152 state_ = SIGNING_IN;
153 if (!ret) {
154 callback_->OnServerConnectionFailure();
155 }
156}
157
158bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
159 if (state_ != CONNECTED)
160 return false;
161
nisseede5da42017-01-12 05:15:36 -0800162 RTC_DCHECK(is_connected());
163 RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000164 if (!is_connected() || peer_id == -1)
165 return false;
166
167 char headers[1024];
168 sprintfn(headers, sizeof(headers),
169 "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
170 "Content-Length: %i\r\n"
171 "Content-Type: text/plain\r\n"
172 "\r\n",
173 my_id_, peer_id, message.length());
174 onconnect_data_ = headers;
175 onconnect_data_ += message;
176 return ConnectControlSocket();
177}
178
179bool PeerConnectionClient::SendHangUp(int peer_id) {
180 return SendToPeer(peer_id, kByeMessage);
181}
182
183bool PeerConnectionClient::IsSendingMessage() {
184 return state_ == CONNECTED &&
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000185 control_socket_->GetState() != rtc::Socket::CS_CLOSED;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000186}
187
188bool PeerConnectionClient::SignOut() {
189 if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
190 return true;
191
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000192 if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000193 hanging_get_->Close();
194
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000195 if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000196 state_ = SIGNING_OUT;
197
198 if (my_id_ != -1) {
199 char buffer[1024];
200 sprintfn(buffer, sizeof(buffer),
201 "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
202 onconnect_data_ = buffer;
203 return ConnectControlSocket();
204 } else {
205 // Can occur if the app is closed before we finish connecting.
206 return true;
207 }
208 } else {
209 state_ = SIGNING_OUT_WAITING;
210 }
211
212 return true;
213}
214
215void PeerConnectionClient::Close() {
216 control_socket_->Close();
217 hanging_get_->Close();
218 onconnect_data_.clear();
219 peers_.clear();
220 if (resolver_ != NULL) {
221 resolver_->Destroy(false);
222 resolver_ = NULL;
223 }
224 my_id_ = -1;
225 state_ = NOT_CONNECTED;
226}
227
228bool PeerConnectionClient::ConnectControlSocket() {
nisseede5da42017-01-12 05:15:36 -0800229 RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000230 int err = control_socket_->Connect(server_address_);
231 if (err == SOCKET_ERROR) {
232 Close();
233 return false;
234 }
235 return true;
236}
237
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000238void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) {
nisseede5da42017-01-12 05:15:36 -0800239 RTC_DCHECK(!onconnect_data_.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000240 size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
nisseede5da42017-01-12 05:15:36 -0800241 RTC_DCHECK(sent == onconnect_data_.length());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000242 onconnect_data_.clear();
243}
244
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000245void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000246 char buffer[1024];
247 sprintfn(buffer, sizeof(buffer),
248 "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000249 int len = static_cast<int>(strlen(buffer));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000250 int sent = socket->Send(buffer, len);
nisseede5da42017-01-12 05:15:36 -0800251 RTC_DCHECK(sent == len);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000252}
253
254void PeerConnectionClient::OnMessageFromPeer(int peer_id,
255 const std::string& message) {
256 if (message.length() == (sizeof(kByeMessage) - 1) &&
257 message.compare(kByeMessage) == 0) {
258 callback_->OnPeerDisconnected(peer_id);
259 } else {
260 callback_->OnMessageFromPeer(peer_id, message);
261 }
262}
263
264bool PeerConnectionClient::GetHeaderValue(const std::string& data,
265 size_t eoh,
266 const char* header_pattern,
267 size_t* value) {
nisseede5da42017-01-12 05:15:36 -0800268 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000269 size_t found = data.find(header_pattern);
270 if (found != std::string::npos && found < eoh) {
271 *value = atoi(&data[found + strlen(header_pattern)]);
272 return true;
273 }
274 return false;
275}
276
277bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
278 const char* header_pattern,
279 std::string* value) {
nisseede5da42017-01-12 05:15:36 -0800280 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000281 size_t found = data.find(header_pattern);
282 if (found != std::string::npos && found < eoh) {
283 size_t begin = found + strlen(header_pattern);
284 size_t end = data.find("\r\n", begin);
285 if (end == std::string::npos)
286 end = eoh;
287 value->assign(data.substr(begin, end - begin));
288 return true;
289 }
290 return false;
291}
292
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000293bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket,
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000294 std::string* data,
295 size_t* content_length) {
296 char buffer[0xffff];
297 do {
Stefan Holmer9131efd2016-05-23 18:19:26 +0200298 int bytes = socket->Recv(buffer, sizeof(buffer), nullptr);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000299 if (bytes <= 0)
300 break;
301 data->append(buffer, bytes);
302 } while (true);
303
304 bool ret = false;
305 size_t i = data->find("\r\n\r\n");
306 if (i != std::string::npos) {
307 LOG(INFO) << "Headers received";
308 if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
309 size_t total_response_size = (i + 4) + *content_length;
310 if (data->length() >= total_response_size) {
311 ret = true;
312 std::string should_close;
313 const char kConnection[] = "\r\nConnection: ";
314 if (GetHeaderValue(*data, i, kConnection, &should_close) &&
315 should_close.compare("close") == 0) {
316 socket->Close();
317 // Since we closed the socket, there was no notification delivered
318 // to us. Compensate by letting ourselves know.
319 OnClose(socket, 0);
320 }
321 } else {
322 // We haven't received everything. Just continue to accept data.
323 }
324 } else {
325 LOG(LS_ERROR) << "No content length field specified by the server.";
326 }
327 }
328 return ret;
329}
330
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000331void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000332 size_t content_length = 0;
333 if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
334 size_t peer_id = 0, eoh = 0;
335 bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
336 &eoh);
337 if (ok) {
338 if (my_id_ == -1) {
339 // First response. Let's store our server assigned ID.
nisseede5da42017-01-12 05:15:36 -0800340 RTC_DCHECK(state_ == SIGNING_IN);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000341 my_id_ = static_cast<int>(peer_id);
nisseede5da42017-01-12 05:15:36 -0800342 RTC_DCHECK(my_id_ != -1);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000343
344 // The body of the response will be a list of already connected peers.
345 if (content_length) {
346 size_t pos = eoh + 4;
347 while (pos < control_data_.size()) {
348 size_t eol = control_data_.find('\n', pos);
349 if (eol == std::string::npos)
350 break;
351 int id = 0;
352 std::string name;
353 bool connected;
354 if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
355 &connected) && id != my_id_) {
356 peers_[id] = name;
357 callback_->OnPeerConnected(id, name);
358 }
359 pos = eol + 1;
360 }
361 }
nisseede5da42017-01-12 05:15:36 -0800362 RTC_DCHECK(is_connected());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000363 callback_->OnSignedIn();
364 } else if (state_ == SIGNING_OUT) {
365 Close();
366 callback_->OnDisconnected();
367 } else if (state_ == SIGNING_OUT_WAITING) {
368 SignOut();
369 }
370 }
371
372 control_data_.clear();
373
374 if (state_ == SIGNING_IN) {
nisseede5da42017-01-12 05:15:36 -0800375 RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000376 state_ = CONNECTED;
377 hanging_get_->Connect(server_address_);
378 }
379 }
380}
381
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000382void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000383 LOG(INFO) << __FUNCTION__;
384 size_t content_length = 0;
385 if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
386 size_t peer_id = 0, eoh = 0;
387 bool ok = ParseServerResponse(notification_data_, content_length,
388 &peer_id, &eoh);
389
390 if (ok) {
391 // Store the position where the body begins.
392 size_t pos = eoh + 4;
393
394 if (my_id_ == static_cast<int>(peer_id)) {
395 // A notification about a new member or a member that just
396 // disconnected.
397 int id = 0;
398 std::string name;
399 bool connected = false;
400 if (ParseEntry(notification_data_.substr(pos), &name, &id,
401 &connected)) {
402 if (connected) {
403 peers_[id] = name;
404 callback_->OnPeerConnected(id, name);
405 } else {
406 peers_.erase(id);
407 callback_->OnPeerDisconnected(id);
408 }
409 }
410 } else {
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000411 OnMessageFromPeer(static_cast<int>(peer_id),
412 notification_data_.substr(pos));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000413 }
414 }
415
416 notification_data_.clear();
417 }
418
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000419 if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED &&
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000420 state_ == CONNECTED) {
421 hanging_get_->Connect(server_address_);
422 }
423}
424
425bool PeerConnectionClient::ParseEntry(const std::string& entry,
426 std::string* name,
427 int* id,
428 bool* connected) {
nisseede5da42017-01-12 05:15:36 -0800429 RTC_DCHECK(name != NULL);
430 RTC_DCHECK(id != NULL);
431 RTC_DCHECK(connected != NULL);
432 RTC_DCHECK(!entry.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000433
434 *connected = false;
435 size_t separator = entry.find(',');
436 if (separator != std::string::npos) {
437 *id = atoi(&entry[separator + 1]);
438 name->assign(entry.substr(0, separator));
439 separator = entry.find(',', separator + 1);
440 if (separator != std::string::npos) {
441 *connected = atoi(&entry[separator + 1]) ? true : false;
442 }
443 }
444 return !name->empty();
445}
446
447int PeerConnectionClient::GetResponseStatus(const std::string& response) {
448 int status = -1;
449 size_t pos = response.find(' ');
450 if (pos != std::string::npos)
451 status = atoi(&response[pos + 1]);
452 return status;
453}
454
455bool PeerConnectionClient::ParseServerResponse(const std::string& response,
456 size_t content_length,
457 size_t* peer_id,
458 size_t* eoh) {
459 int status = GetResponseStatus(response.c_str());
460 if (status != 200) {
461 LOG(LS_ERROR) << "Received error from server";
462 Close();
463 callback_->OnDisconnected();
464 return false;
465 }
466
467 *eoh = response.find("\r\n\r\n");
nisseede5da42017-01-12 05:15:36 -0800468 RTC_DCHECK(*eoh != std::string::npos);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000469 if (*eoh == std::string::npos)
470 return false;
471
472 *peer_id = -1;
473
474 // See comment in peer_channel.cc for why we use the Pragma header and
475 // not e.g. "X-Peer-Id".
476 GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
477
478 return true;
479}
480
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000481void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000482 LOG(INFO) << __FUNCTION__;
483
484 socket->Close();
485
486#ifdef WIN32
487 if (err != WSAECONNREFUSED) {
488#else
489 if (err != ECONNREFUSED) {
490#endif
491 if (socket == hanging_get_.get()) {
492 if (state_ == CONNECTED) {
493 hanging_get_->Close();
494 hanging_get_->Connect(server_address_);
495 }
496 } else {
497 callback_->OnMessageSent(err);
498 }
499 } else {
500 if (socket == control_socket_.get()) {
501 LOG(WARNING) << "Connection refused; retrying in 2 seconds";
Taylor Brandstetter5d97a9a2016-06-10 14:17:27 -0700502 rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, kReconnectDelay, this,
503 0);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000504 } else {
505 Close();
506 callback_->OnDisconnected();
507 }
508 }
509}
510
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000511void PeerConnectionClient::OnMessage(rtc::Message* msg) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000512 // ignore msg; there is currently only one supported message ("retry")
513 DoConnect();
514}