blob: d6a1033c26e398d5ee24cec0bf9352e20095a38c [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
Donald E Curtisa8736442015-08-05 15:48:13 -070013#include "webrtc/examples/peerconnection/client/defaults.h"
nisseede5da42017-01-12 05:15:36 -080014#include "webrtc/base/checks.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000015#include "webrtc/base/common.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000016#include "webrtc/base/logging.h"
buildbot@webrtc.orga09a9992014-08-13 17:26:08 +000017#include "webrtc/base/nethelpers.h"
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000018#include "webrtc/base/stringutils.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000019
20#ifdef WIN32
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000021#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());
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000242 RTC_UNUSED(sent);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000243 onconnect_data_.clear();
244}
245
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000246void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000247 char buffer[1024];
248 sprintfn(buffer, sizeof(buffer),
249 "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000250 int len = static_cast<int>(strlen(buffer));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000251 int sent = socket->Send(buffer, len);
nisseede5da42017-01-12 05:15:36 -0800252 RTC_DCHECK(sent == len);
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000253 RTC_UNUSED2(sent, len);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000254}
255
256void PeerConnectionClient::OnMessageFromPeer(int peer_id,
257 const std::string& message) {
258 if (message.length() == (sizeof(kByeMessage) - 1) &&
259 message.compare(kByeMessage) == 0) {
260 callback_->OnPeerDisconnected(peer_id);
261 } else {
262 callback_->OnMessageFromPeer(peer_id, message);
263 }
264}
265
266bool PeerConnectionClient::GetHeaderValue(const std::string& data,
267 size_t eoh,
268 const char* header_pattern,
269 size_t* value) {
nisseede5da42017-01-12 05:15:36 -0800270 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000271 size_t found = data.find(header_pattern);
272 if (found != std::string::npos && found < eoh) {
273 *value = atoi(&data[found + strlen(header_pattern)]);
274 return true;
275 }
276 return false;
277}
278
279bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
280 const char* header_pattern,
281 std::string* value) {
nisseede5da42017-01-12 05:15:36 -0800282 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000283 size_t found = data.find(header_pattern);
284 if (found != std::string::npos && found < eoh) {
285 size_t begin = found + strlen(header_pattern);
286 size_t end = data.find("\r\n", begin);
287 if (end == std::string::npos)
288 end = eoh;
289 value->assign(data.substr(begin, end - begin));
290 return true;
291 }
292 return false;
293}
294
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000295bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket,
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000296 std::string* data,
297 size_t* content_length) {
298 char buffer[0xffff];
299 do {
Stefan Holmer9131efd2016-05-23 18:19:26 +0200300 int bytes = socket->Recv(buffer, sizeof(buffer), nullptr);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000301 if (bytes <= 0)
302 break;
303 data->append(buffer, bytes);
304 } while (true);
305
306 bool ret = false;
307 size_t i = data->find("\r\n\r\n");
308 if (i != std::string::npos) {
309 LOG(INFO) << "Headers received";
310 if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
311 size_t total_response_size = (i + 4) + *content_length;
312 if (data->length() >= total_response_size) {
313 ret = true;
314 std::string should_close;
315 const char kConnection[] = "\r\nConnection: ";
316 if (GetHeaderValue(*data, i, kConnection, &should_close) &&
317 should_close.compare("close") == 0) {
318 socket->Close();
319 // Since we closed the socket, there was no notification delivered
320 // to us. Compensate by letting ourselves know.
321 OnClose(socket, 0);
322 }
323 } else {
324 // We haven't received everything. Just continue to accept data.
325 }
326 } else {
327 LOG(LS_ERROR) << "No content length field specified by the server.";
328 }
329 }
330 return ret;
331}
332
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000333void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000334 size_t content_length = 0;
335 if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
336 size_t peer_id = 0, eoh = 0;
337 bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
338 &eoh);
339 if (ok) {
340 if (my_id_ == -1) {
341 // First response. Let's store our server assigned ID.
nisseede5da42017-01-12 05:15:36 -0800342 RTC_DCHECK(state_ == SIGNING_IN);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000343 my_id_ = static_cast<int>(peer_id);
nisseede5da42017-01-12 05:15:36 -0800344 RTC_DCHECK(my_id_ != -1);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000345
346 // The body of the response will be a list of already connected peers.
347 if (content_length) {
348 size_t pos = eoh + 4;
349 while (pos < control_data_.size()) {
350 size_t eol = control_data_.find('\n', pos);
351 if (eol == std::string::npos)
352 break;
353 int id = 0;
354 std::string name;
355 bool connected;
356 if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
357 &connected) && id != my_id_) {
358 peers_[id] = name;
359 callback_->OnPeerConnected(id, name);
360 }
361 pos = eol + 1;
362 }
363 }
nisseede5da42017-01-12 05:15:36 -0800364 RTC_DCHECK(is_connected());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000365 callback_->OnSignedIn();
366 } else if (state_ == SIGNING_OUT) {
367 Close();
368 callback_->OnDisconnected();
369 } else if (state_ == SIGNING_OUT_WAITING) {
370 SignOut();
371 }
372 }
373
374 control_data_.clear();
375
376 if (state_ == SIGNING_IN) {
nisseede5da42017-01-12 05:15:36 -0800377 RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000378 state_ = CONNECTED;
379 hanging_get_->Connect(server_address_);
380 }
381 }
382}
383
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000384void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000385 LOG(INFO) << __FUNCTION__;
386 size_t content_length = 0;
387 if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
388 size_t peer_id = 0, eoh = 0;
389 bool ok = ParseServerResponse(notification_data_, content_length,
390 &peer_id, &eoh);
391
392 if (ok) {
393 // Store the position where the body begins.
394 size_t pos = eoh + 4;
395
396 if (my_id_ == static_cast<int>(peer_id)) {
397 // A notification about a new member or a member that just
398 // disconnected.
399 int id = 0;
400 std::string name;
401 bool connected = false;
402 if (ParseEntry(notification_data_.substr(pos), &name, &id,
403 &connected)) {
404 if (connected) {
405 peers_[id] = name;
406 callback_->OnPeerConnected(id, name);
407 } else {
408 peers_.erase(id);
409 callback_->OnPeerDisconnected(id);
410 }
411 }
412 } else {
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000413 OnMessageFromPeer(static_cast<int>(peer_id),
414 notification_data_.substr(pos));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000415 }
416 }
417
418 notification_data_.clear();
419 }
420
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000421 if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED &&
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000422 state_ == CONNECTED) {
423 hanging_get_->Connect(server_address_);
424 }
425}
426
427bool PeerConnectionClient::ParseEntry(const std::string& entry,
428 std::string* name,
429 int* id,
430 bool* connected) {
nisseede5da42017-01-12 05:15:36 -0800431 RTC_DCHECK(name != NULL);
432 RTC_DCHECK(id != NULL);
433 RTC_DCHECK(connected != NULL);
434 RTC_DCHECK(!entry.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000435
436 *connected = false;
437 size_t separator = entry.find(',');
438 if (separator != std::string::npos) {
439 *id = atoi(&entry[separator + 1]);
440 name->assign(entry.substr(0, separator));
441 separator = entry.find(',', separator + 1);
442 if (separator != std::string::npos) {
443 *connected = atoi(&entry[separator + 1]) ? true : false;
444 }
445 }
446 return !name->empty();
447}
448
449int PeerConnectionClient::GetResponseStatus(const std::string& response) {
450 int status = -1;
451 size_t pos = response.find(' ');
452 if (pos != std::string::npos)
453 status = atoi(&response[pos + 1]);
454 return status;
455}
456
457bool PeerConnectionClient::ParseServerResponse(const std::string& response,
458 size_t content_length,
459 size_t* peer_id,
460 size_t* eoh) {
461 int status = GetResponseStatus(response.c_str());
462 if (status != 200) {
463 LOG(LS_ERROR) << "Received error from server";
464 Close();
465 callback_->OnDisconnected();
466 return false;
467 }
468
469 *eoh = response.find("\r\n\r\n");
nisseede5da42017-01-12 05:15:36 -0800470 RTC_DCHECK(*eoh != std::string::npos);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000471 if (*eoh == std::string::npos)
472 return false;
473
474 *peer_id = -1;
475
476 // See comment in peer_channel.cc for why we use the Pragma header and
477 // not e.g. "X-Peer-Id".
478 GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
479
480 return true;
481}
482
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000483void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000484 LOG(INFO) << __FUNCTION__;
485
486 socket->Close();
487
488#ifdef WIN32
489 if (err != WSAECONNREFUSED) {
490#else
491 if (err != ECONNREFUSED) {
492#endif
493 if (socket == hanging_get_.get()) {
494 if (state_ == CONNECTED) {
495 hanging_get_->Close();
496 hanging_get_->Connect(server_address_);
497 }
498 } else {
499 callback_->OnMessageSent(err);
500 }
501 } else {
502 if (socket == control_socket_.get()) {
503 LOG(WARNING) << "Connection refused; retrying in 2 seconds";
Taylor Brandstetter5d97a9a2016-06-10 14:17:27 -0700504 rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, kReconnectDelay, this,
505 0);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000506 } else {
507 Close();
508 callback_->OnDisconnected();
509 }
510 }
511}
512
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000513void PeerConnectionClient::OnMessage(rtc::Message* msg) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000514 // ignore msg; there is currently only one supported message ("retry")
515 DoConnect();
516}