Home | History | Annotate | Download | only in client
      1 /*
      2  * libjingle
      3  * Copyright 2012, 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 "talk/examples/peerconnection/client/peer_connection_client.h"
     29 
     30 #include "talk/examples/peerconnection/client/defaults.h"
     31 #include "webrtc/base/common.h"
     32 #include "webrtc/base/logging.h"
     33 #include "webrtc/base/nethelpers.h"
     34 #include "webrtc/base/stringutils.h"
     35 
     36 #ifdef WIN32
     37 #include "webrtc/base/win32socketserver.h"
     38 #endif
     39 
     40 using rtc::sprintfn;
     41 
     42 namespace {
     43 
     44 // This is our magical hangup signal.
     45 const char kByeMessage[] = "BYE";
     46 // Delay between server connection retries, in milliseconds
     47 const int kReconnectDelay = 2000;
     48 
     49 rtc::AsyncSocket* CreateClientSocket(int family) {
     50 #ifdef WIN32
     51   rtc::Win32Socket* sock = new rtc::Win32Socket();
     52   sock->CreateT(family, SOCK_STREAM);
     53   return sock;
     54 #elif defined(POSIX)
     55   rtc::Thread* thread = rtc::Thread::Current();
     56   ASSERT(thread != NULL);
     57   return thread->socketserver()->CreateAsyncSocket(family, SOCK_STREAM);
     58 #else
     59 #error Platform not supported.
     60 #endif
     61 }
     62 
     63 }
     64 
     65 PeerConnectionClient::PeerConnectionClient()
     66   : callback_(NULL),
     67     resolver_(NULL),
     68     state_(NOT_CONNECTED),
     69     my_id_(-1) {
     70 }
     71 
     72 PeerConnectionClient::~PeerConnectionClient() {
     73 }
     74 
     75 void PeerConnectionClient::InitSocketSignals() {
     76   ASSERT(control_socket_.get() != NULL);
     77   ASSERT(hanging_get_.get() != NULL);
     78   control_socket_->SignalCloseEvent.connect(this,
     79       &PeerConnectionClient::OnClose);
     80   hanging_get_->SignalCloseEvent.connect(this,
     81       &PeerConnectionClient::OnClose);
     82   control_socket_->SignalConnectEvent.connect(this,
     83       &PeerConnectionClient::OnConnect);
     84   hanging_get_->SignalConnectEvent.connect(this,
     85       &PeerConnectionClient::OnHangingGetConnect);
     86   control_socket_->SignalReadEvent.connect(this,
     87       &PeerConnectionClient::OnRead);
     88   hanging_get_->SignalReadEvent.connect(this,
     89       &PeerConnectionClient::OnHangingGetRead);
     90 }
     91 
     92 int PeerConnectionClient::id() const {
     93   return my_id_;
     94 }
     95 
     96 bool PeerConnectionClient::is_connected() const {
     97   return my_id_ != -1;
     98 }
     99 
    100 const Peers& PeerConnectionClient::peers() const {
    101   return peers_;
    102 }
    103 
    104 void PeerConnectionClient::RegisterObserver(
    105     PeerConnectionClientObserver* callback) {
    106   ASSERT(!callback_);
    107   callback_ = callback;
    108 }
    109 
    110 void PeerConnectionClient::Connect(const std::string& server, int port,
    111                                    const std::string& client_name) {
    112   ASSERT(!server.empty());
    113   ASSERT(!client_name.empty());
    114 
    115   if (state_ != NOT_CONNECTED) {
    116     LOG(WARNING)
    117         << "The client must not be connected before you can call Connect()";
    118     callback_->OnServerConnectionFailure();
    119     return;
    120   }
    121 
    122   if (server.empty() || client_name.empty()) {
    123     callback_->OnServerConnectionFailure();
    124     return;
    125   }
    126 
    127   if (port <= 0)
    128     port = kDefaultServerPort;
    129 
    130   server_address_.SetIP(server);
    131   server_address_.SetPort(port);
    132   client_name_ = client_name;
    133 
    134   if (server_address_.IsUnresolved()) {
    135     state_ = RESOLVING;
    136     resolver_ = new rtc::AsyncResolver();
    137     resolver_->SignalDone.connect(this, &PeerConnectionClient::OnResolveResult);
    138     resolver_->Start(server_address_);
    139   } else {
    140     DoConnect();
    141   }
    142 }
    143 
    144 void PeerConnectionClient::OnResolveResult(
    145     rtc::AsyncResolverInterface* resolver) {
    146   if (resolver_->GetError() != 0) {
    147     callback_->OnServerConnectionFailure();
    148     resolver_->Destroy(false);
    149     resolver_ = NULL;
    150     state_ = NOT_CONNECTED;
    151   } else {
    152     server_address_ = resolver_->address();
    153     DoConnect();
    154   }
    155 }
    156 
    157 void PeerConnectionClient::DoConnect() {
    158   control_socket_.reset(CreateClientSocket(server_address_.ipaddr().family()));
    159   hanging_get_.reset(CreateClientSocket(server_address_.ipaddr().family()));
    160   InitSocketSignals();
    161   char buffer[1024];
    162   sprintfn(buffer, sizeof(buffer),
    163            "GET /sign_in?%s HTTP/1.0\r\n\r\n", client_name_.c_str());
    164   onconnect_data_ = buffer;
    165 
    166   bool ret = ConnectControlSocket();
    167   if (ret)
    168     state_ = SIGNING_IN;
    169   if (!ret) {
    170     callback_->OnServerConnectionFailure();
    171   }
    172 }
    173 
    174 bool PeerConnectionClient::SendToPeer(int peer_id, const std::string& message) {
    175   if (state_ != CONNECTED)
    176     return false;
    177 
    178   ASSERT(is_connected());
    179   ASSERT(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
    180   if (!is_connected() || peer_id == -1)
    181     return false;
    182 
    183   char headers[1024];
    184   sprintfn(headers, sizeof(headers),
    185       "POST /message?peer_id=%i&to=%i HTTP/1.0\r\n"
    186       "Content-Length: %i\r\n"
    187       "Content-Type: text/plain\r\n"
    188       "\r\n",
    189       my_id_, peer_id, message.length());
    190   onconnect_data_ = headers;
    191   onconnect_data_ += message;
    192   return ConnectControlSocket();
    193 }
    194 
    195 bool PeerConnectionClient::SendHangUp(int peer_id) {
    196   return SendToPeer(peer_id, kByeMessage);
    197 }
    198 
    199 bool PeerConnectionClient::IsSendingMessage() {
    200   return state_ == CONNECTED &&
    201          control_socket_->GetState() != rtc::Socket::CS_CLOSED;
    202 }
    203 
    204 bool PeerConnectionClient::SignOut() {
    205   if (state_ == NOT_CONNECTED || state_ == SIGNING_OUT)
    206     return true;
    207 
    208   if (hanging_get_->GetState() != rtc::Socket::CS_CLOSED)
    209     hanging_get_->Close();
    210 
    211   if (control_socket_->GetState() == rtc::Socket::CS_CLOSED) {
    212     state_ = SIGNING_OUT;
    213 
    214     if (my_id_ != -1) {
    215       char buffer[1024];
    216       sprintfn(buffer, sizeof(buffer),
    217           "GET /sign_out?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
    218       onconnect_data_ = buffer;
    219       return ConnectControlSocket();
    220     } else {
    221       // Can occur if the app is closed before we finish connecting.
    222       return true;
    223     }
    224   } else {
    225     state_ = SIGNING_OUT_WAITING;
    226   }
    227 
    228   return true;
    229 }
    230 
    231 void PeerConnectionClient::Close() {
    232   control_socket_->Close();
    233   hanging_get_->Close();
    234   onconnect_data_.clear();
    235   peers_.clear();
    236   if (resolver_ != NULL) {
    237     resolver_->Destroy(false);
    238     resolver_ = NULL;
    239   }
    240   my_id_ = -1;
    241   state_ = NOT_CONNECTED;
    242 }
    243 
    244 bool PeerConnectionClient::ConnectControlSocket() {
    245   ASSERT(control_socket_->GetState() == rtc::Socket::CS_CLOSED);
    246   int err = control_socket_->Connect(server_address_);
    247   if (err == SOCKET_ERROR) {
    248     Close();
    249     return false;
    250   }
    251   return true;
    252 }
    253 
    254 void PeerConnectionClient::OnConnect(rtc::AsyncSocket* socket) {
    255   ASSERT(!onconnect_data_.empty());
    256   size_t sent = socket->Send(onconnect_data_.c_str(), onconnect_data_.length());
    257   ASSERT(sent == onconnect_data_.length());
    258   RTC_UNUSED(sent);
    259   onconnect_data_.clear();
    260 }
    261 
    262 void PeerConnectionClient::OnHangingGetConnect(rtc::AsyncSocket* socket) {
    263   char buffer[1024];
    264   sprintfn(buffer, sizeof(buffer),
    265            "GET /wait?peer_id=%i HTTP/1.0\r\n\r\n", my_id_);
    266   int len = static_cast<int>(strlen(buffer));
    267   int sent = socket->Send(buffer, len);
    268   ASSERT(sent == len);
    269   RTC_UNUSED2(sent, len);
    270 }
    271 
    272 void PeerConnectionClient::OnMessageFromPeer(int peer_id,
    273                                              const std::string& message) {
    274   if (message.length() == (sizeof(kByeMessage) - 1) &&
    275       message.compare(kByeMessage) == 0) {
    276     callback_->OnPeerDisconnected(peer_id);
    277   } else {
    278     callback_->OnMessageFromPeer(peer_id, message);
    279   }
    280 }
    281 
    282 bool PeerConnectionClient::GetHeaderValue(const std::string& data,
    283                                           size_t eoh,
    284                                           const char* header_pattern,
    285                                           size_t* value) {
    286   ASSERT(value != NULL);
    287   size_t found = data.find(header_pattern);
    288   if (found != std::string::npos && found < eoh) {
    289     *value = atoi(&data[found + strlen(header_pattern)]);
    290     return true;
    291   }
    292   return false;
    293 }
    294 
    295 bool PeerConnectionClient::GetHeaderValue(const std::string& data, size_t eoh,
    296                                           const char* header_pattern,
    297                                           std::string* value) {
    298   ASSERT(value != NULL);
    299   size_t found = data.find(header_pattern);
    300   if (found != std::string::npos && found < eoh) {
    301     size_t begin = found + strlen(header_pattern);
    302     size_t end = data.find("\r\n", begin);
    303     if (end == std::string::npos)
    304       end = eoh;
    305     value->assign(data.substr(begin, end - begin));
    306     return true;
    307   }
    308   return false;
    309 }
    310 
    311 bool PeerConnectionClient::ReadIntoBuffer(rtc::AsyncSocket* socket,
    312                                           std::string* data,
    313                                           size_t* content_length) {
    314   char buffer[0xffff];
    315   do {
    316     int bytes = socket->Recv(buffer, sizeof(buffer));
    317     if (bytes <= 0)
    318       break;
    319     data->append(buffer, bytes);
    320   } while (true);
    321 
    322   bool ret = false;
    323   size_t i = data->find("\r\n\r\n");
    324   if (i != std::string::npos) {
    325     LOG(INFO) << "Headers received";
    326     if (GetHeaderValue(*data, i, "\r\nContent-Length: ", content_length)) {
    327       size_t total_response_size = (i + 4) + *content_length;
    328       if (data->length() >= total_response_size) {
    329         ret = true;
    330         std::string should_close;
    331         const char kConnection[] = "\r\nConnection: ";
    332         if (GetHeaderValue(*data, i, kConnection, &should_close) &&
    333             should_close.compare("close") == 0) {
    334           socket->Close();
    335           // Since we closed the socket, there was no notification delivered
    336           // to us.  Compensate by letting ourselves know.
    337           OnClose(socket, 0);
    338         }
    339       } else {
    340         // We haven't received everything.  Just continue to accept data.
    341       }
    342     } else {
    343       LOG(LS_ERROR) << "No content length field specified by the server.";
    344     }
    345   }
    346   return ret;
    347 }
    348 
    349 void PeerConnectionClient::OnRead(rtc::AsyncSocket* socket) {
    350   size_t content_length = 0;
    351   if (ReadIntoBuffer(socket, &control_data_, &content_length)) {
    352     size_t peer_id = 0, eoh = 0;
    353     bool ok = ParseServerResponse(control_data_, content_length, &peer_id,
    354                                   &eoh);
    355     if (ok) {
    356       if (my_id_ == -1) {
    357         // First response.  Let's store our server assigned ID.
    358         ASSERT(state_ == SIGNING_IN);
    359         my_id_ = static_cast<int>(peer_id);
    360         ASSERT(my_id_ != -1);
    361 
    362         // The body of the response will be a list of already connected peers.
    363         if (content_length) {
    364           size_t pos = eoh + 4;
    365           while (pos < control_data_.size()) {
    366             size_t eol = control_data_.find('\n', pos);
    367             if (eol == std::string::npos)
    368               break;
    369             int id = 0;
    370             std::string name;
    371             bool connected;
    372             if (ParseEntry(control_data_.substr(pos, eol - pos), &name, &id,
    373                            &connected) && id != my_id_) {
    374               peers_[id] = name;
    375               callback_->OnPeerConnected(id, name);
    376             }
    377             pos = eol + 1;
    378           }
    379         }
    380         ASSERT(is_connected());
    381         callback_->OnSignedIn();
    382       } else if (state_ == SIGNING_OUT) {
    383         Close();
    384         callback_->OnDisconnected();
    385       } else if (state_ == SIGNING_OUT_WAITING) {
    386         SignOut();
    387       }
    388     }
    389 
    390     control_data_.clear();
    391 
    392     if (state_ == SIGNING_IN) {
    393       ASSERT(hanging_get_->GetState() == rtc::Socket::CS_CLOSED);
    394       state_ = CONNECTED;
    395       hanging_get_->Connect(server_address_);
    396     }
    397   }
    398 }
    399 
    400 void PeerConnectionClient::OnHangingGetRead(rtc::AsyncSocket* socket) {
    401   LOG(INFO) << __FUNCTION__;
    402   size_t content_length = 0;
    403   if (ReadIntoBuffer(socket, &notification_data_, &content_length)) {
    404     size_t peer_id = 0, eoh = 0;
    405     bool ok = ParseServerResponse(notification_data_, content_length,
    406                                   &peer_id, &eoh);
    407 
    408     if (ok) {
    409       // Store the position where the body begins.
    410       size_t pos = eoh + 4;
    411 
    412       if (my_id_ == static_cast<int>(peer_id)) {
    413         // A notification about a new member or a member that just
    414         // disconnected.
    415         int id = 0;
    416         std::string name;
    417         bool connected = false;
    418         if (ParseEntry(notification_data_.substr(pos), &name, &id,
    419                        &connected)) {
    420           if (connected) {
    421             peers_[id] = name;
    422             callback_->OnPeerConnected(id, name);
    423           } else {
    424             peers_.erase(id);
    425             callback_->OnPeerDisconnected(id);
    426           }
    427         }
    428       } else {
    429         OnMessageFromPeer(static_cast<int>(peer_id),
    430                           notification_data_.substr(pos));
    431       }
    432     }
    433 
    434     notification_data_.clear();
    435   }
    436 
    437   if (hanging_get_->GetState() == rtc::Socket::CS_CLOSED &&
    438       state_ == CONNECTED) {
    439     hanging_get_->Connect(server_address_);
    440   }
    441 }
    442 
    443 bool PeerConnectionClient::ParseEntry(const std::string& entry,
    444                                       std::string* name,
    445                                       int* id,
    446                                       bool* connected) {
    447   ASSERT(name != NULL);
    448   ASSERT(id != NULL);
    449   ASSERT(connected != NULL);
    450   ASSERT(!entry.empty());
    451 
    452   *connected = false;
    453   size_t separator = entry.find(',');
    454   if (separator != std::string::npos) {
    455     *id = atoi(&entry[separator + 1]);
    456     name->assign(entry.substr(0, separator));
    457     separator = entry.find(',', separator + 1);
    458     if (separator != std::string::npos) {
    459       *connected = atoi(&entry[separator + 1]) ? true : false;
    460     }
    461   }
    462   return !name->empty();
    463 }
    464 
    465 int PeerConnectionClient::GetResponseStatus(const std::string& response) {
    466   int status = -1;
    467   size_t pos = response.find(' ');
    468   if (pos != std::string::npos)
    469     status = atoi(&response[pos + 1]);
    470   return status;
    471 }
    472 
    473 bool PeerConnectionClient::ParseServerResponse(const std::string& response,
    474                                                size_t content_length,
    475                                                size_t* peer_id,
    476                                                size_t* eoh) {
    477   int status = GetResponseStatus(response.c_str());
    478   if (status != 200) {
    479     LOG(LS_ERROR) << "Received error from server";
    480     Close();
    481     callback_->OnDisconnected();
    482     return false;
    483   }
    484 
    485   *eoh = response.find("\r\n\r\n");
    486   ASSERT(*eoh != std::string::npos);
    487   if (*eoh == std::string::npos)
    488     return false;
    489 
    490   *peer_id = -1;
    491 
    492   // See comment in peer_channel.cc for why we use the Pragma header and
    493   // not e.g. "X-Peer-Id".
    494   GetHeaderValue(response, *eoh, "\r\nPragma: ", peer_id);
    495 
    496   return true;
    497 }
    498 
    499 void PeerConnectionClient::OnClose(rtc::AsyncSocket* socket, int err) {
    500   LOG(INFO) << __FUNCTION__;
    501 
    502   socket->Close();
    503 
    504 #ifdef WIN32
    505   if (err != WSAECONNREFUSED) {
    506 #else
    507   if (err != ECONNREFUSED) {
    508 #endif
    509     if (socket == hanging_get_.get()) {
    510       if (state_ == CONNECTED) {
    511         hanging_get_->Close();
    512         hanging_get_->Connect(server_address_);
    513       }
    514     } else {
    515       callback_->OnMessageSent(err);
    516     }
    517   } else {
    518     if (socket == control_socket_.get()) {
    519       LOG(WARNING) << "Connection refused; retrying in 2 seconds";
    520       rtc::Thread::Current()->PostDelayed(kReconnectDelay, this, 0);
    521     } else {
    522       Close();
    523       callback_->OnDisconnected();
    524     }
    525   }
    526 }
    527 
    528 void PeerConnectionClient::OnMessage(rtc::Message* msg) {
    529   // ignore msg; there is currently only one supported message ("retry")
    530   DoConnect();
    531 }
    532