blob: 24ae5e515b2078220d53b8066ca7611d216c9f92 [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
nissebc8feda2017-06-29 06:21:20 -070013#include "webrtc/examples/peerconnection/client/defaults.h"
Edward Lemurc20978e2017-07-06 19:44:34 +020014#include "webrtc/rtc_base/checks.h"
15#include "webrtc/rtc_base/logging.h"
16#include "webrtc/rtc_base/nethelpers.h"
17#include "webrtc/rtc_base/stringutils.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000018
19#ifdef WIN32
Edward Lemurc20978e2017-07-06 19:44:34 +020020#include "webrtc/rtc_base/win32socketserver.h"
henrike@webrtc.org28e20752013-07-10 00:45:36 +000021#endif
22
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000023using rtc::sprintfn;
henrike@webrtc.org28e20752013-07-10 00:45:36 +000024
25namespace {
26
27// This is our magical hangup signal.
28const char kByeMessage[] = "BYE";
29// Delay between server connection retries, in milliseconds
30const int kReconnectDelay = 2000;
31
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000032rtc::AsyncSocket* CreateClientSocket(int family) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +000033#ifdef WIN32
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000034 rtc::Win32Socket* sock = new rtc::Win32Socket();
henrike@webrtc.org28e20752013-07-10 00:45:36 +000035 sock->CreateT(family, SOCK_STREAM);
36 return sock;
Thiago Farina91543732015-04-20 13:14:36 +020037#elif defined(WEBRTC_POSIX)
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +000038 rtc::Thread* thread = rtc::Thread::Current();
nisseede5da42017-01-12 05:15:36 -080039 RTC_DCHECK(thread != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000040 return thread->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
41#else
42#error Platform not supported.
43#endif
44}
45
jbauch70625e52015-12-09 14:18:14 -080046} // namespace
henrike@webrtc.org28e20752013-07-10 00:45:36 +000047
48PeerConnectionClient::PeerConnectionClient()
49 : callback_(NULL),
50 resolver_(NULL),
51 state_(NOT_CONNECTED),
52 my_id_(-1) {
53}
54
55PeerConnectionClient::~PeerConnectionClient() {
56}
57
58void PeerConnectionClient::InitSocketSignals() {
nisseede5da42017-01-12 05:15:36 -080059 RTC_DCHECK(control_socket_.get() != NULL);
60 RTC_DCHECK(hanging_get_.get() != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000061 control_socket_->SignalCloseEvent.connect(this,
62 &PeerConnectionClient::OnClose);
63 hanging_get_->SignalCloseEvent.connect(this,
64 &PeerConnectionClient::OnClose);
65 control_socket_->SignalConnectEvent.connect(this,
66 &PeerConnectionClient::OnConnect);
67 hanging_get_->SignalConnectEvent.connect(this,
68 &PeerConnectionClient::OnHangingGetConnect);
69 control_socket_->SignalReadEvent.connect(this,
70 &PeerConnectionClient::OnRead);
71 hanging_get_->SignalReadEvent.connect(this,
72 &PeerConnectionClient::OnHangingGetRead);
73}
74
75int PeerConnectionClient::id() const {
76 return my_id_;
77}
78
79bool PeerConnectionClient::is_connected() const {
80 return my_id_ != -1;
81}
82
83const Peers& PeerConnectionClient::peers() const {
84 return peers_;
85}
86
87void PeerConnectionClient::RegisterObserver(
88 PeerConnectionClientObserver* callback) {
nisseede5da42017-01-12 05:15:36 -080089 RTC_DCHECK(!callback_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +000090 callback_ = callback;
91}
92
93void PeerConnectionClient::Connect(const std::string& server, int port,
94 const std::string& client_name) {
nisseede5da42017-01-12 05:15:36 -080095 RTC_DCHECK(!server.empty());
96 RTC_DCHECK(!client_name.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +000097
98 if (state_ != NOT_CONNECTED) {
99 LOG(WARNING)
100 << "The client must not be connected before you can call Connect()";
101 callback_->OnServerConnectionFailure();
102 return;
103 }
104
105 if (server.empty() || client_name.empty()) {
106 callback_->OnServerConnectionFailure();
107 return;
108 }
109
110 if (port <= 0)
111 port = kDefaultServerPort;
112
113 server_address_.SetIP(server);
114 server_address_.SetPort(port);
115 client_name_ = client_name;
116
tfarina20a34612015-11-02 16:20:22 -0800117 if (server_address_.IsUnresolvedIP()) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000118 state_ = RESOLVING;
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000119 resolver_ = new rtc::AsyncResolver();
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000120 resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult);
121 resolver_->Start(server_address_);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000122 } else {
123 DoConnect();
124 }
125}
126
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000127void PeerConnectionClient::OnResolveResult(
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000128 rtc::AsyncResolverInterface* resolver) {
sergeyu@chromium.orga23f0ca2013-11-13 22:48:52 +0000129 if (resolver_->GetError() != 0) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000130 callback_->OnServerConnectionFailure();
131 resolver_->Destroy(false);
132 resolver_ = NULL;
133 state_ = NOT_CONNECTED;
134 } else {
135 server_address_ = resolver_->address();
136 DoConnect();
137 }
138}
139
140void PeerConnectionClient::DoConnect() {
141 control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
142 hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
143 InitSocketSignals();
144 char buffer[1024];
145 sprintfn(buffer, sizeof(buffer),
146 "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str());
147 onconnect_data_ = buffer;
148
149 bool ret = ConnectControlSocket();
150 if (ret)
151 state_ = SIGNING_IN;
152 if (!ret) {
153 callback_->OnServerConnectionFailure();
154 }
155}
156
157bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
158 if (state_ != CONNECTED)
159 return false;
160
nisseede5da42017-01-12 05:15:36 -0800161 RTC_DCHECK(is_connected());
162 RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000163 if (!is_connected() || peer_id == -1)
164 return false;
165
166 char headers[1024];
167 sprintfn(headers, sizeof(headers),
168 "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
169 "Content-Length: %i\r\n"
170 "Content-Type: text/plain\r\n"
171 "\r\n",
172 my_id_, peer_id, message.length());
173 onconnect_data_ = headers;
174 onconnect_data_ += message;
175 return ConnectControlSocket();
176}
177
178bool PeerConnectionClient::SendHangUp(int peer_id) {
179 return SendToPeer(peer_id, kByeMessage);
180}
181
182bool PeerConnectionClient::IsSendingMessage() {
183 return state_ == CONNECTED &&
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000184 control_socket_->GetState() != rtc::Socket::CS_CLOSED;
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000185}
186
187bool PeerConnectionClient::SignOut() {
188 if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
189 return true;
190
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000191 if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED)
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000192 hanging_get_->Close();
193
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000194 if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000195 state_ = SIGNING_OUT;
196
197 if (my_id_ != -1) {
198 char buffer[1024];
199 sprintfn(buffer, sizeof(buffer),
200 "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
201 onconnect_data_ = buffer;
202 return ConnectControlSocket();
203 } else {
204 // Can occur if the app is closed before we finish connecting.
205 return true;
206 }
207 } else {
208 state_ = SIGNING_OUT_WAITING;
209 }
210
211 return true;
212}
213
214void PeerConnectionClient::Close() {
215 control_socket_->Close();
216 hanging_get_->Close();
217 onconnect_data_.clear();
218 peers_.clear();
219 if (resolver_ != NULL) {
220 resolver_->Destroy(false);
221 resolver_ = NULL;
222 }
223 my_id_ = -1;
224 state_ = NOT_CONNECTED;
225}
226
227bool PeerConnectionClient::ConnectControlSocket() {
nisseede5da42017-01-12 05:15:36 -0800228 RTC_DCHECK(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000229 int err = control_socket_->Connect(server_address_);
230 if (err == SOCKET_ERROR) {
231 Close();
232 return false;
233 }
234 return true;
235}
236
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000237void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) {
nisseede5da42017-01-12 05:15:36 -0800238 RTC_DCHECK(!onconnect_data_.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000239 size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
nisseede5da42017-01-12 05:15:36 -0800240 RTC_DCHECK(sent == onconnect_data_.length());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000241 onconnect_data_.clear();
242}
243
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000244void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000245 char buffer[1024];
246 sprintfn(buffer, sizeof(buffer),
247 "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000248 int len = static_cast<int>(strlen(buffer));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000249 int sent = socket->Send(buffer, len);
nisseede5da42017-01-12 05:15:36 -0800250 RTC_DCHECK(sent == len);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000251}
252
253void PeerConnectionClient::OnMessageFromPeer(int peer_id,
254 const std::string& message) {
255 if (message.length() == (sizeof(kByeMessage) - 1) &&
256 message.compare(kByeMessage) == 0) {
257 callback_->OnPeerDisconnected(peer_id);
258 } else {
259 callback_->OnMessageFromPeer(peer_id, message);
260 }
261}
262
263bool PeerConnectionClient::GetHeaderValue(const std::string& data,
264 size_t eoh,
265 const char* header_pattern,
266 size_t* value) {
nisseede5da42017-01-12 05:15:36 -0800267 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000268 size_t found = data.find(header_pattern);
269 if (found != std::string::npos && found < eoh) {
270 *value = atoi(&data[found + strlen(header_pattern)]);
271 return true;
272 }
273 return false;
274}
275
276bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
277 const char* header_pattern,
278 std::string* value) {
nisseede5da42017-01-12 05:15:36 -0800279 RTC_DCHECK(value != NULL);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000280 size_t found = data.find(header_pattern);
281 if (found != std::string::npos && found < eoh) {
282 size_t begin = found + strlen(header_pattern);
283 size_t end = data.find("\r\n", begin);
284 if (end == std::string::npos)
285 end = eoh;
286 value->assign(data.substr(begin, end - begin));
287 return true;
288 }
289 return false;
290}
291
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000292bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket,
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000293 std::string* data,
294 size_t* content_length) {
295 char buffer[0xffff];
296 do {
Stefan Holmer9131efd2016-05-23 18:19:26 +0200297 int bytes = socket->Recv(buffer, sizeof(buffer), nullptr);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000298 if (bytes <= 0)
299 break;
300 data->append(buffer, bytes);
301 } while (true);
302
303 bool ret = false;
304 size_t i = data->find("\r\n\r\n");
305 if (i != std::string::npos) {
306 LOG(INFO) << "Headers received";
307 if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
308 size_t total_response_size = (i + 4) + *content_length;
309 if (data->length() >= total_response_size) {
310 ret = true;
311 std::string should_close;
312 const char kConnection[] = "\r\nConnection: ";
313 if (GetHeaderValue(*data, i, kConnection, &should_close) &&
314 should_close.compare("close") == 0) {
315 socket->Close();
316 // Since we closed the socket, there was no notification delivered
317 // to us. Compensate by letting ourselves know.
318 OnClose(socket, 0);
319 }
320 } else {
321 // We haven't received everything. Just continue to accept data.
322 }
323 } else {
324 LOG(LS_ERROR) << "No content length field specified by the server.";
325 }
326 }
327 return ret;
328}
329
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000330void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000331 size_t content_length = 0;
332 if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
333 size_t peer_id = 0, eoh = 0;
334 bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
335 &eoh);
336 if (ok) {
337 if (my_id_ == -1) {
338 // First response. Let's store our server assigned ID.
nisseede5da42017-01-12 05:15:36 -0800339 RTC_DCHECK(state_ == SIGNING_IN);
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000340 my_id_ = static_cast<int>(peer_id);
nisseede5da42017-01-12 05:15:36 -0800341 RTC_DCHECK(my_id_ != -1);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000342
343 // The body of the response will be a list of already connected peers.
344 if (content_length) {
345 size_t pos = eoh + 4;
346 while (pos < control_data_.size()) {
347 size_t eol = control_data_.find('\n', pos);
348 if (eol == std::string::npos)
349 break;
350 int id = 0;
351 std::string name;
352 bool connected;
353 if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
354 &connected) && id != my_id_) {
355 peers_[id] = name;
356 callback_->OnPeerConnected(id, name);
357 }
358 pos = eol + 1;
359 }
360 }
nisseede5da42017-01-12 05:15:36 -0800361 RTC_DCHECK(is_connected());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000362 callback_->OnSignedIn();
363 } else if (state_ == SIGNING_OUT) {
364 Close();
365 callback_->OnDisconnected();
366 } else if (state_ == SIGNING_OUT_WAITING) {
367 SignOut();
368 }
369 }
370
371 control_data_.clear();
372
373 if (state_ == SIGNING_IN) {
nisseede5da42017-01-12 05:15:36 -0800374 RTC_DCHECK(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000375 state_ = CONNECTED;
376 hanging_get_->Connect(server_address_);
377 }
378 }
379}
380
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000381void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000382 LOG(INFO) << __FUNCTION__;
383 size_t content_length = 0;
384 if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
385 size_t peer_id = 0, eoh = 0;
386 bool ok = ParseServerResponse(notification_data_, content_length,
387 &peer_id, &eoh);
388
389 if (ok) {
390 // Store the position where the body begins.
391 size_t pos = eoh + 4;
392
393 if (my_id_ == static_cast<int>(peer_id)) {
394 // A notification about a new member or a member that just
395 // disconnected.
396 int id = 0;
397 std::string name;
398 bool connected = false;
399 if (ParseEntry(notification_data_.substr(pos), &name, &id,
400 &connected)) {
401 if (connected) {
402 peers_[id] = name;
403 callback_->OnPeerConnected(id, name);
404 } else {
405 peers_.erase(id);
406 callback_->OnPeerDisconnected(id);
407 }
408 }
409 } else {
henrike@webrtc.org28654cb2013-07-22 21:07:49 +0000410 OnMessageFromPeer(static_cast<int>(peer_id),
411 notification_data_.substr(pos));
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000412 }
413 }
414
415 notification_data_.clear();
416 }
417
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000418 if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED &&
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000419 state_ == CONNECTED) {
420 hanging_get_->Connect(server_address_);
421 }
422}
423
424bool PeerConnectionClient::ParseEntry(const std::string& entry,
425 std::string* name,
426 int* id,
427 bool* connected) {
nisseede5da42017-01-12 05:15:36 -0800428 RTC_DCHECK(name != NULL);
429 RTC_DCHECK(id != NULL);
430 RTC_DCHECK(connected != NULL);
431 RTC_DCHECK(!entry.empty());
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000432
433 *connected = false;
434 size_t separator = entry.find(',');
435 if (separator != std::string::npos) {
436 *id = atoi(&entry[separator + 1]);
437 name->assign(entry.substr(0, separator));
438 separator = entry.find(',', separator + 1);
439 if (separator != std::string::npos) {
440 *connected = atoi(&entry[separator + 1]) ? true : false;
441 }
442 }
443 return !name->empty();
444}
445
446int PeerConnectionClient::GetResponseStatus(const std::string& response) {
447 int status = -1;
448 size_t pos = response.find(' ');
449 if (pos != std::string::npos)
450 status = atoi(&response[pos + 1]);
451 return status;
452}
453
454bool PeerConnectionClient::ParseServerResponse(const std::string& response,
455 size_t content_length,
456 size_t* peer_id,
457 size_t* eoh) {
458 int status = GetResponseStatus(response.c_str());
459 if (status != 200) {
460 LOG(LS_ERROR) << "Received error from server";
461 Close();
462 callback_->OnDisconnected();
463 return false;
464 }
465
466 *eoh = response.find("\r\n\r\n");
nisseede5da42017-01-12 05:15:36 -0800467 RTC_DCHECK(*eoh != std::string::npos);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000468 if (*eoh == std::string::npos)
469 return false;
470
471 *peer_id = -1;
472
473 // See comment in peer_channel.cc for why we use the Pragma header and
474 // not e.g. "X-Peer-Id".
475 GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
476
477 return true;
478}
479
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000480void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000481 LOG(INFO) << __FUNCTION__;
482
483 socket->Close();
484
485#ifdef WIN32
486 if (err != WSAECONNREFUSED) {
487#else
488 if (err != ECONNREFUSED) {
489#endif
490 if (socket == hanging_get_.get()) {
491 if (state_ == CONNECTED) {
492 hanging_get_->Close();
493 hanging_get_->Connect(server_address_);
494 }
495 } else {
496 callback_->OnMessageSent(err);
497 }
498 } else {
499 if (socket == control_socket_.get()) {
500 LOG(WARNING) << "Connection refused; retrying in 2 seconds";
Taylor Brandstetter5d97a9a2016-06-10 14:17:27 -0700501 rtc::Thread::Current()->PostDelayed(RTC_FROM_HERE, kReconnectDelay, this,
502 0);
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000503 } else {
504 Close();
505 callback_->OnDisconnected();
506 }
507 }
508}
509
buildbot@webrtc.orgd4e598d2014-07-29 17:36:52 +0000510void PeerConnectionClient::OnMessage(rtc::Message* msg) {
henrike@webrtc.org28e20752013-07-10 00:45:36 +0000511 // ignore msg; there is currently only one supported message ("retry")
512 DoConnect();
513}