henrike@webrtc.org | 28e2075 | 2013-07-10 00:45:36 +0000 | [diff] [blame] | 1 | /* |
| 2 | * libjingle |
| 3 | * Copyright 2004--2008, 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/base/basicdefs.h" |
| 29 | #include "talk/base/basictypes.h" |
| 30 | #include "talk/base/common.h" |
| 31 | #include "talk/base/helpers.h" |
| 32 | #include "talk/base/logging.h" |
| 33 | #include "talk/base/stringutils.h" |
| 34 | #include "talk/p2p/base/constants.h" |
| 35 | #include "talk/p2p/base/transportchannel.h" |
| 36 | #include "talk/xmllite/xmlelement.h" |
| 37 | #include "pseudotcpchannel.h" |
| 38 | #include "tunnelsessionclient.h" |
| 39 | |
| 40 | namespace cricket { |
| 41 | |
| 42 | const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel"; |
| 43 | const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" }; |
| 44 | const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" }; |
| 45 | const char CN_TUNNEL[] = "tunnel"; |
| 46 | |
| 47 | enum { |
| 48 | MSG_CLOCK = 1, |
| 49 | MSG_DESTROY, |
| 50 | MSG_TERMINATE, |
| 51 | MSG_EVENT, |
| 52 | MSG_CREATE_TUNNEL, |
| 53 | }; |
| 54 | |
| 55 | struct EventData : public talk_base::MessageData { |
| 56 | int event, error; |
| 57 | EventData(int ev, int err = 0) : event(ev), error(err) { } |
| 58 | }; |
| 59 | |
| 60 | struct CreateTunnelData : public talk_base::MessageData { |
| 61 | buzz::Jid jid; |
| 62 | std::string description; |
| 63 | talk_base::Thread* thread; |
| 64 | talk_base::StreamInterface* stream; |
| 65 | }; |
| 66 | |
| 67 | extern const talk_base::ConstantLabel SESSION_STATES[]; |
| 68 | |
| 69 | const talk_base::ConstantLabel SESSION_STATES[] = { |
| 70 | KLABEL(Session::STATE_INIT), |
| 71 | KLABEL(Session::STATE_SENTINITIATE), |
| 72 | KLABEL(Session::STATE_RECEIVEDINITIATE), |
| 73 | KLABEL(Session::STATE_SENTACCEPT), |
| 74 | KLABEL(Session::STATE_RECEIVEDACCEPT), |
| 75 | KLABEL(Session::STATE_SENTMODIFY), |
| 76 | KLABEL(Session::STATE_RECEIVEDMODIFY), |
| 77 | KLABEL(Session::STATE_SENTREJECT), |
| 78 | KLABEL(Session::STATE_RECEIVEDREJECT), |
| 79 | KLABEL(Session::STATE_SENTREDIRECT), |
| 80 | KLABEL(Session::STATE_SENTTERMINATE), |
| 81 | KLABEL(Session::STATE_RECEIVEDTERMINATE), |
| 82 | KLABEL(Session::STATE_INPROGRESS), |
| 83 | KLABEL(Session::STATE_DEINIT), |
| 84 | LASTLABEL |
| 85 | }; |
| 86 | |
| 87 | /////////////////////////////////////////////////////////////////////////////// |
| 88 | // TunnelContentDescription |
| 89 | /////////////////////////////////////////////////////////////////////////////// |
| 90 | |
| 91 | struct TunnelContentDescription : public ContentDescription { |
| 92 | std::string description; |
| 93 | |
| 94 | TunnelContentDescription(const std::string& desc) : description(desc) { } |
| 95 | virtual ContentDescription* Copy() const { |
| 96 | return new TunnelContentDescription(*this); |
| 97 | } |
| 98 | }; |
| 99 | |
| 100 | /////////////////////////////////////////////////////////////////////////////// |
| 101 | // TunnelSessionClientBase |
| 102 | /////////////////////////////////////////////////////////////////////////////// |
| 103 | |
| 104 | TunnelSessionClientBase::TunnelSessionClientBase(const buzz::Jid& jid, |
| 105 | SessionManager* manager, const std::string &ns) |
| 106 | : jid_(jid), session_manager_(manager), namespace_(ns), shutdown_(false) { |
| 107 | session_manager_->AddClient(namespace_, this); |
| 108 | } |
| 109 | |
| 110 | TunnelSessionClientBase::~TunnelSessionClientBase() { |
| 111 | shutdown_ = true; |
| 112 | for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| 113 | it != sessions_.end(); |
| 114 | ++it) { |
| 115 | Session* session = (*it)->ReleaseSession(true); |
| 116 | session_manager_->DestroySession(session); |
| 117 | } |
| 118 | session_manager_->RemoveClient(namespace_); |
| 119 | } |
| 120 | |
| 121 | void TunnelSessionClientBase::OnSessionCreate(Session* session, bool received) { |
| 122 | LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionCreate: received=" |
| 123 | << received; |
| 124 | ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| 125 | if (received) |
| 126 | sessions_.push_back( |
| 127 | MakeTunnelSession(session, talk_base::Thread::Current(), RESPONDER)); |
| 128 | } |
| 129 | |
| 130 | void TunnelSessionClientBase::OnSessionDestroy(Session* session) { |
| 131 | LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionDestroy"; |
| 132 | ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| 133 | if (shutdown_) |
| 134 | return; |
| 135 | for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| 136 | it != sessions_.end(); |
| 137 | ++it) { |
| 138 | if ((*it)->HasSession(session)) { |
| 139 | VERIFY((*it)->ReleaseSession(false) == session); |
| 140 | sessions_.erase(it); |
| 141 | return; |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | |
| 146 | talk_base::StreamInterface* TunnelSessionClientBase::CreateTunnel( |
| 147 | const buzz::Jid& to, const std::string& description) { |
| 148 | // Valid from any thread |
| 149 | CreateTunnelData data; |
| 150 | data.jid = to; |
| 151 | data.description = description; |
| 152 | data.thread = talk_base::Thread::Current(); |
| 153 | data.stream = NULL; |
| 154 | session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data); |
| 155 | return data.stream; |
| 156 | } |
| 157 | |
| 158 | talk_base::StreamInterface* TunnelSessionClientBase::AcceptTunnel( |
| 159 | Session* session) { |
| 160 | ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| 161 | TunnelSession* tunnel = NULL; |
| 162 | for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| 163 | it != sessions_.end(); |
| 164 | ++it) { |
| 165 | if ((*it)->HasSession(session)) { |
| 166 | tunnel = *it; |
| 167 | break; |
| 168 | } |
| 169 | } |
| 170 | ASSERT(tunnel != NULL); |
| 171 | |
| 172 | SessionDescription* answer = CreateAnswer(session->remote_description()); |
| 173 | if (answer == NULL) |
| 174 | return NULL; |
| 175 | |
| 176 | session->Accept(answer); |
| 177 | return tunnel->GetStream(); |
| 178 | } |
| 179 | |
| 180 | void TunnelSessionClientBase::DeclineTunnel(Session* session) { |
| 181 | ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| 182 | session->Reject(STR_TERMINATE_DECLINE); |
| 183 | } |
| 184 | |
| 185 | void TunnelSessionClientBase::OnMessage(talk_base::Message* pmsg) { |
| 186 | if (pmsg->message_id == MSG_CREATE_TUNNEL) { |
| 187 | ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| 188 | CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata); |
| 189 | SessionDescription* offer = CreateOffer(data->jid, data->description); |
| 190 | if (offer == NULL) { |
| 191 | return; |
| 192 | } |
| 193 | |
| 194 | Session* session = session_manager_->CreateSession(jid_.Str(), namespace_); |
| 195 | TunnelSession* tunnel = MakeTunnelSession(session, data->thread, |
| 196 | INITIATOR); |
| 197 | sessions_.push_back(tunnel); |
| 198 | session->Initiate(data->jid.Str(), offer); |
| 199 | data->stream = tunnel->GetStream(); |
| 200 | } |
| 201 | } |
| 202 | |
| 203 | TunnelSession* TunnelSessionClientBase::MakeTunnelSession( |
| 204 | Session* session, talk_base::Thread* stream_thread, |
| 205 | TunnelSessionRole /*role*/) { |
| 206 | return new TunnelSession(this, session, stream_thread); |
| 207 | } |
| 208 | |
| 209 | /////////////////////////////////////////////////////////////////////////////// |
| 210 | // TunnelSessionClient |
| 211 | /////////////////////////////////////////////////////////////////////////////// |
| 212 | |
| 213 | TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid, |
| 214 | SessionManager* manager, |
| 215 | const std::string &ns) |
| 216 | : TunnelSessionClientBase(jid, manager, ns) { |
| 217 | } |
| 218 | |
| 219 | TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid, |
| 220 | SessionManager* manager) |
| 221 | : TunnelSessionClientBase(jid, manager, NS_TUNNEL) { |
| 222 | } |
| 223 | |
| 224 | TunnelSessionClient::~TunnelSessionClient() { |
| 225 | } |
| 226 | |
| 227 | |
| 228 | bool TunnelSessionClient::ParseContent(SignalingProtocol protocol, |
| 229 | const buzz::XmlElement* elem, |
| 230 | ContentDescription** content, |
| 231 | ParseError* error) { |
| 232 | if (const buzz::XmlElement* type_elem = elem->FirstNamed(QN_TUNNEL_TYPE)) { |
| 233 | *content = new TunnelContentDescription(type_elem->BodyText()); |
| 234 | return true; |
| 235 | } |
| 236 | return false; |
| 237 | } |
| 238 | |
| 239 | bool TunnelSessionClient::WriteContent( |
| 240 | SignalingProtocol protocol, |
| 241 | const ContentDescription* untyped_content, |
| 242 | buzz::XmlElement** elem, WriteError* error) { |
| 243 | const TunnelContentDescription* content = |
| 244 | static_cast<const TunnelContentDescription*>(untyped_content); |
| 245 | |
| 246 | buzz::XmlElement* root = new buzz::XmlElement(QN_TUNNEL_DESCRIPTION, true); |
| 247 | buzz::XmlElement* type_elem = new buzz::XmlElement(QN_TUNNEL_TYPE); |
| 248 | type_elem->SetBodyText(content->description); |
| 249 | root->AddElement(type_elem); |
| 250 | *elem = root; |
| 251 | return true; |
| 252 | } |
| 253 | |
| 254 | SessionDescription* NewTunnelSessionDescription( |
| 255 | const std::string& content_name, ContentDescription* content) { |
| 256 | SessionDescription* sdesc = new SessionDescription(); |
| 257 | sdesc->AddContent(content_name, NS_TUNNEL, content); |
| 258 | return sdesc; |
| 259 | } |
| 260 | |
| 261 | bool FindTunnelContent(const cricket::SessionDescription* sdesc, |
| 262 | std::string* name, |
| 263 | const TunnelContentDescription** content) { |
| 264 | const ContentInfo* cinfo = sdesc->FirstContentByType(NS_TUNNEL); |
| 265 | if (cinfo == NULL) |
| 266 | return false; |
| 267 | |
| 268 | *name = cinfo->name; |
| 269 | *content = static_cast<const TunnelContentDescription*>( |
| 270 | cinfo->description); |
| 271 | return true; |
| 272 | } |
| 273 | |
| 274 | void TunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid, |
| 275 | Session *session) { |
| 276 | std::string content_name; |
| 277 | const TunnelContentDescription* content = NULL; |
| 278 | if (!FindTunnelContent(session->remote_description(), |
| 279 | &content_name, &content)) { |
| 280 | session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS); |
| 281 | return; |
| 282 | } |
| 283 | |
| 284 | SignalIncomingTunnel(this, jid, content->description, session); |
| 285 | } |
| 286 | |
| 287 | SessionDescription* TunnelSessionClient::CreateOffer( |
| 288 | const buzz::Jid &jid, const std::string &description) { |
| 289 | SessionDescription* offer = NewTunnelSessionDescription( |
| 290 | CN_TUNNEL, new TunnelContentDescription(description)); |
| 291 | talk_base::scoped_ptr<TransportDescription> tdesc( |
| 292 | session_manager_->transport_desc_factory()->CreateOffer( |
| 293 | TransportOptions(), NULL)); |
| 294 | if (tdesc.get()) { |
| 295 | offer->AddTransportInfo(TransportInfo(CN_TUNNEL, *tdesc)); |
| 296 | } else { |
| 297 | delete offer; |
| 298 | offer = NULL; |
| 299 | } |
| 300 | return offer; |
| 301 | } |
| 302 | |
| 303 | SessionDescription* TunnelSessionClient::CreateAnswer( |
| 304 | const SessionDescription* offer) { |
| 305 | std::string content_name; |
| 306 | const TunnelContentDescription* offer_tunnel = NULL; |
| 307 | if (!FindTunnelContent(offer, &content_name, &offer_tunnel)) |
| 308 | return NULL; |
| 309 | |
| 310 | SessionDescription* answer = NewTunnelSessionDescription( |
| 311 | content_name, new TunnelContentDescription(offer_tunnel->description)); |
| 312 | const TransportInfo* tinfo = offer->GetTransportInfoByName(content_name); |
| 313 | if (tinfo) { |
| 314 | const TransportDescription* offer_tdesc = &tinfo->description; |
| 315 | ASSERT(offer_tdesc != NULL); |
| 316 | talk_base::scoped_ptr<TransportDescription> tdesc( |
| 317 | session_manager_->transport_desc_factory()->CreateAnswer( |
| 318 | offer_tdesc, TransportOptions(), NULL)); |
| 319 | if (tdesc.get()) { |
| 320 | answer->AddTransportInfo(TransportInfo(content_name, *tdesc)); |
| 321 | } else { |
| 322 | delete answer; |
| 323 | answer = NULL; |
| 324 | } |
| 325 | } |
| 326 | return answer; |
| 327 | } |
| 328 | /////////////////////////////////////////////////////////////////////////////// |
| 329 | // TunnelSession |
| 330 | /////////////////////////////////////////////////////////////////////////////// |
| 331 | |
| 332 | // |
| 333 | // Signalling thread methods |
| 334 | // |
| 335 | |
| 336 | TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session, |
| 337 | talk_base::Thread* stream_thread) |
| 338 | : client_(client), session_(session), channel_(NULL) { |
| 339 | ASSERT(client_ != NULL); |
| 340 | ASSERT(session_ != NULL); |
| 341 | session_->SignalState.connect(this, &TunnelSession::OnSessionState); |
| 342 | channel_ = new PseudoTcpChannel(stream_thread, session_); |
| 343 | channel_->SignalChannelClosed.connect(this, &TunnelSession::OnChannelClosed); |
| 344 | } |
| 345 | |
| 346 | TunnelSession::~TunnelSession() { |
| 347 | ASSERT(client_ != NULL); |
| 348 | ASSERT(session_ == NULL); |
| 349 | ASSERT(channel_ == NULL); |
| 350 | } |
| 351 | |
| 352 | talk_base::StreamInterface* TunnelSession::GetStream() { |
| 353 | ASSERT(channel_ != NULL); |
| 354 | return channel_->GetStream(); |
| 355 | } |
| 356 | |
| 357 | bool TunnelSession::HasSession(Session* session) { |
| 358 | ASSERT(NULL != session_); |
| 359 | return (session_ == session); |
| 360 | } |
| 361 | |
| 362 | Session* TunnelSession::ReleaseSession(bool channel_exists) { |
| 363 | ASSERT(NULL != session_); |
| 364 | ASSERT(NULL != channel_); |
| 365 | Session* session = session_; |
| 366 | session_->SignalState.disconnect(this); |
| 367 | session_ = NULL; |
| 368 | if (channel_exists) |
| 369 | channel_->SignalChannelClosed.disconnect(this); |
| 370 | channel_ = NULL; |
| 371 | delete this; |
| 372 | return session; |
| 373 | } |
| 374 | |
| 375 | void TunnelSession::OnSessionState(BaseSession* session, |
| 376 | BaseSession::State state) { |
| 377 | LOG(LS_INFO) << "TunnelSession::OnSessionState(" |
| 378 | << talk_base::nonnull( |
| 379 | talk_base::FindLabel(state, SESSION_STATES), "Unknown") |
| 380 | << ")"; |
| 381 | ASSERT(session == session_); |
| 382 | |
| 383 | switch (state) { |
| 384 | case Session::STATE_RECEIVEDINITIATE: |
| 385 | OnInitiate(); |
| 386 | break; |
| 387 | case Session::STATE_SENTACCEPT: |
| 388 | case Session::STATE_RECEIVEDACCEPT: |
| 389 | OnAccept(); |
| 390 | break; |
| 391 | case Session::STATE_SENTTERMINATE: |
| 392 | case Session::STATE_RECEIVEDTERMINATE: |
| 393 | OnTerminate(); |
| 394 | break; |
| 395 | case Session::STATE_DEINIT: |
| 396 | // ReleaseSession should have been called before this. |
| 397 | ASSERT(false); |
| 398 | break; |
| 399 | default: |
| 400 | break; |
| 401 | } |
| 402 | } |
| 403 | |
| 404 | void TunnelSession::OnInitiate() { |
| 405 | ASSERT(client_ != NULL); |
| 406 | ASSERT(session_ != NULL); |
| 407 | client_->OnIncomingTunnel(buzz::Jid(session_->remote_name()), session_); |
| 408 | } |
| 409 | |
| 410 | void TunnelSession::OnAccept() { |
| 411 | ASSERT(channel_ != NULL); |
| 412 | const ContentInfo* content = |
| 413 | session_->remote_description()->FirstContentByType(NS_TUNNEL); |
| 414 | ASSERT(content != NULL); |
| 415 | VERIFY(channel_->Connect( |
| 416 | content->name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT)); |
| 417 | } |
| 418 | |
| 419 | void TunnelSession::OnTerminate() { |
| 420 | ASSERT(channel_ != NULL); |
| 421 | channel_->OnSessionTerminate(session_); |
| 422 | } |
| 423 | |
| 424 | void TunnelSession::OnChannelClosed(PseudoTcpChannel* channel) { |
| 425 | ASSERT(channel_ == channel); |
| 426 | ASSERT(session_ != NULL); |
| 427 | session_->Terminate(); |
| 428 | } |
| 429 | |
| 430 | /////////////////////////////////////////////////////////////////////////////// |
| 431 | |
| 432 | } // namespace cricket |