| /* |
| * libjingle |
| * Copyright 2004--2008, Google Inc. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright notice, |
| * this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. |
| * 3. The name of the author may not be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |
| * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |
| * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
| * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |
| * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "webrtc/base/basicdefs.h" |
| #include "webrtc/base/basictypes.h" |
| #include "webrtc/base/common.h" |
| #include "webrtc/base/helpers.h" |
| #include "webrtc/base/logging.h" |
| #include "webrtc/base/stringutils.h" |
| #include "talk/p2p/base/constants.h" |
| #include "talk/p2p/base/transportchannel.h" |
| #include "talk/xmllite/xmlelement.h" |
| #include "pseudotcpchannel.h" |
| #include "tunnelsessionclient.h" |
| |
| namespace cricket { |
| |
| const char NS_TUNNEL[] = "http://www.google.com/talk/tunnel"; |
| const buzz::StaticQName QN_TUNNEL_DESCRIPTION = { NS_TUNNEL, "description" }; |
| const buzz::StaticQName QN_TUNNEL_TYPE = { NS_TUNNEL, "type" }; |
| const char CN_TUNNEL[] = "tunnel"; |
| |
| enum { |
| MSG_CLOCK = 1, |
| MSG_DESTROY, |
| MSG_TERMINATE, |
| MSG_EVENT, |
| MSG_CREATE_TUNNEL, |
| }; |
| |
| struct EventData : public rtc::MessageData { |
| int event, error; |
| EventData(int ev, int err = 0) : event(ev), error(err) { } |
| }; |
| |
| struct CreateTunnelData : public rtc::MessageData { |
| buzz::Jid jid; |
| std::string description; |
| rtc::Thread* thread; |
| rtc::StreamInterface* stream; |
| }; |
| |
| extern const rtc::ConstantLabel SESSION_STATES[]; |
| |
| const rtc::ConstantLabel SESSION_STATES[] = { |
| KLABEL(Session::STATE_INIT), |
| KLABEL(Session::STATE_SENTINITIATE), |
| KLABEL(Session::STATE_RECEIVEDINITIATE), |
| KLABEL(Session::STATE_SENTACCEPT), |
| KLABEL(Session::STATE_RECEIVEDACCEPT), |
| KLABEL(Session::STATE_SENTMODIFY), |
| KLABEL(Session::STATE_RECEIVEDMODIFY), |
| KLABEL(Session::STATE_SENTREJECT), |
| KLABEL(Session::STATE_RECEIVEDREJECT), |
| KLABEL(Session::STATE_SENTREDIRECT), |
| KLABEL(Session::STATE_SENTTERMINATE), |
| KLABEL(Session::STATE_RECEIVEDTERMINATE), |
| KLABEL(Session::STATE_INPROGRESS), |
| KLABEL(Session::STATE_DEINIT), |
| LASTLABEL |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // TunnelContentDescription |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| struct TunnelContentDescription : public ContentDescription { |
| std::string description; |
| |
| TunnelContentDescription(const std::string& desc) : description(desc) { } |
| virtual ContentDescription* Copy() const { |
| return new TunnelContentDescription(*this); |
| } |
| }; |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // TunnelSessionClientBase |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| TunnelSessionClientBase::TunnelSessionClientBase(const buzz::Jid& jid, |
| SessionManager* manager, const std::string &ns) |
| : jid_(jid), session_manager_(manager), namespace_(ns), shutdown_(false) { |
| session_manager_->AddClient(namespace_, this); |
| } |
| |
| TunnelSessionClientBase::~TunnelSessionClientBase() { |
| shutdown_ = true; |
| for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| it != sessions_.end(); |
| ++it) { |
| Session* session = (*it)->ReleaseSession(true); |
| session_manager_->DestroySession(session); |
| } |
| session_manager_->RemoveClient(namespace_); |
| } |
| |
| void TunnelSessionClientBase::OnSessionCreate(Session* session, bool received) { |
| LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionCreate: received=" |
| << received; |
| ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| if (received) |
| sessions_.push_back( |
| MakeTunnelSession(session, rtc::Thread::Current(), RESPONDER)); |
| } |
| |
| void TunnelSessionClientBase::OnSessionDestroy(Session* session) { |
| LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionDestroy"; |
| ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| if (shutdown_) |
| return; |
| for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| it != sessions_.end(); |
| ++it) { |
| if ((*it)->HasSession(session)) { |
| VERIFY((*it)->ReleaseSession(false) == session); |
| sessions_.erase(it); |
| return; |
| } |
| } |
| } |
| |
| rtc::StreamInterface* TunnelSessionClientBase::CreateTunnel( |
| const buzz::Jid& to, const std::string& description) { |
| // Valid from any thread |
| CreateTunnelData data; |
| data.jid = to; |
| data.description = description; |
| data.thread = rtc::Thread::Current(); |
| data.stream = NULL; |
| session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data); |
| return data.stream; |
| } |
| |
| rtc::StreamInterface* TunnelSessionClientBase::AcceptTunnel( |
| Session* session) { |
| ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| TunnelSession* tunnel = NULL; |
| for (std::vector<TunnelSession*>::iterator it = sessions_.begin(); |
| it != sessions_.end(); |
| ++it) { |
| if ((*it)->HasSession(session)) { |
| tunnel = *it; |
| break; |
| } |
| } |
| ASSERT(tunnel != NULL); |
| |
| SessionDescription* answer = CreateAnswer(session->remote_description()); |
| if (answer == NULL) |
| return NULL; |
| |
| session->Accept(answer); |
| return tunnel->GetStream(); |
| } |
| |
| void TunnelSessionClientBase::DeclineTunnel(Session* session) { |
| ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| session->Reject(STR_TERMINATE_DECLINE); |
| } |
| |
| void TunnelSessionClientBase::OnMessage(rtc::Message* pmsg) { |
| if (pmsg->message_id == MSG_CREATE_TUNNEL) { |
| ASSERT(session_manager_->signaling_thread()->IsCurrent()); |
| CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata); |
| SessionDescription* offer = CreateOffer(data->jid, data->description); |
| if (offer == NULL) { |
| return; |
| } |
| |
| Session* session = session_manager_->CreateSession(jid_.Str(), namespace_); |
| TunnelSession* tunnel = MakeTunnelSession(session, data->thread, |
| INITIATOR); |
| sessions_.push_back(tunnel); |
| session->Initiate(data->jid.Str(), offer); |
| data->stream = tunnel->GetStream(); |
| } |
| } |
| |
| TunnelSession* TunnelSessionClientBase::MakeTunnelSession( |
| Session* session, rtc::Thread* stream_thread, |
| TunnelSessionRole /*role*/) { |
| return new TunnelSession(this, session, stream_thread); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| // TunnelSessionClient |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid, |
| SessionManager* manager, |
| const std::string &ns) |
| : TunnelSessionClientBase(jid, manager, ns) { |
| } |
| |
| TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid, |
| SessionManager* manager) |
| : TunnelSessionClientBase(jid, manager, NS_TUNNEL) { |
| } |
| |
| TunnelSessionClient::~TunnelSessionClient() { |
| } |
| |
| |
| bool TunnelSessionClient::ParseContent(SignalingProtocol protocol, |
| const buzz::XmlElement* elem, |
| ContentDescription** content, |
| ParseError* error) { |
| if (const buzz::XmlElement* type_elem = elem->FirstNamed(QN_TUNNEL_TYPE)) { |
| *content = new TunnelContentDescription(type_elem->BodyText()); |
| return true; |
| } |
| return false; |
| } |
| |
| bool TunnelSessionClient::WriteContent( |
| SignalingProtocol protocol, |
| const ContentDescription* untyped_content, |
| buzz::XmlElement** elem, WriteError* error) { |
| const TunnelContentDescription* content = |
| static_cast<const TunnelContentDescription*>(untyped_content); |
| |
| buzz::XmlElement* root = new buzz::XmlElement(QN_TUNNEL_DESCRIPTION, true); |
| buzz::XmlElement* type_elem = new buzz::XmlElement(QN_TUNNEL_TYPE); |
| type_elem->SetBodyText(content->description); |
| root->AddElement(type_elem); |
| *elem = root; |
| return true; |
| } |
| |
| SessionDescription* NewTunnelSessionDescription( |
| const std::string& content_name, ContentDescription* content) { |
| SessionDescription* sdesc = new SessionDescription(); |
| sdesc->AddContent(content_name, NS_TUNNEL, content); |
| return sdesc; |
| } |
| |
| bool FindTunnelContent(const cricket::SessionDescription* sdesc, |
| std::string* name, |
| const TunnelContentDescription** content) { |
| const ContentInfo* cinfo = sdesc->FirstContentByType(NS_TUNNEL); |
| if (cinfo == NULL) |
| return false; |
| |
| *name = cinfo->name; |
| *content = static_cast<const TunnelContentDescription*>( |
| cinfo->description); |
| return true; |
| } |
| |
| void TunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid, |
| Session *session) { |
| std::string content_name; |
| const TunnelContentDescription* content = NULL; |
| if (!FindTunnelContent(session->remote_description(), |
| &content_name, &content)) { |
| session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS); |
| return; |
| } |
| |
| SignalIncomingTunnel(this, jid, content->description, session); |
| } |
| |
| SessionDescription* TunnelSessionClient::CreateOffer( |
| const buzz::Jid &jid, const std::string &description) { |
| SessionDescription* offer = NewTunnelSessionDescription( |
| CN_TUNNEL, new TunnelContentDescription(description)); |
| rtc::scoped_ptr<TransportDescription> tdesc( |
| session_manager_->transport_desc_factory()->CreateOffer( |
| TransportOptions(), NULL)); |
| if (tdesc.get()) { |
| offer->AddTransportInfo(TransportInfo(CN_TUNNEL, *tdesc)); |
| } else { |
| delete offer; |
| offer = NULL; |
| } |
| return offer; |
| } |
| |
| SessionDescription* TunnelSessionClient::CreateAnswer( |
| const SessionDescription* offer) { |
| std::string content_name; |
| const TunnelContentDescription* offer_tunnel = NULL; |
| if (!FindTunnelContent(offer, &content_name, &offer_tunnel)) |
| return NULL; |
| |
| SessionDescription* answer = NewTunnelSessionDescription( |
| content_name, new TunnelContentDescription(offer_tunnel->description)); |
| const TransportInfo* tinfo = offer->GetTransportInfoByName(content_name); |
| if (tinfo) { |
| const TransportDescription* offer_tdesc = &tinfo->description; |
| ASSERT(offer_tdesc != NULL); |
| rtc::scoped_ptr<TransportDescription> tdesc( |
| session_manager_->transport_desc_factory()->CreateAnswer( |
| offer_tdesc, TransportOptions(), NULL)); |
| if (tdesc.get()) { |
| answer->AddTransportInfo(TransportInfo(content_name, *tdesc)); |
| } else { |
| delete answer; |
| answer = NULL; |
| } |
| } |
| return answer; |
| } |
| /////////////////////////////////////////////////////////////////////////////// |
| // TunnelSession |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| // |
| // Signalling thread methods |
| // |
| |
| TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session, |
| rtc::Thread* stream_thread) |
| : client_(client), session_(session), channel_(NULL) { |
| ASSERT(client_ != NULL); |
| ASSERT(session_ != NULL); |
| session_->SignalState.connect(this, &TunnelSession::OnSessionState); |
| channel_ = new PseudoTcpChannel(stream_thread, session_); |
| channel_->SignalChannelClosed.connect(this, &TunnelSession::OnChannelClosed); |
| } |
| |
| TunnelSession::~TunnelSession() { |
| ASSERT(client_ != NULL); |
| ASSERT(session_ == NULL); |
| ASSERT(channel_ == NULL); |
| } |
| |
| rtc::StreamInterface* TunnelSession::GetStream() { |
| ASSERT(channel_ != NULL); |
| return channel_->GetStream(); |
| } |
| |
| bool TunnelSession::HasSession(Session* session) { |
| ASSERT(NULL != session_); |
| return (session_ == session); |
| } |
| |
| Session* TunnelSession::ReleaseSession(bool channel_exists) { |
| ASSERT(NULL != session_); |
| ASSERT(NULL != channel_); |
| Session* session = session_; |
| session_->SignalState.disconnect(this); |
| session_ = NULL; |
| if (channel_exists) |
| channel_->SignalChannelClosed.disconnect(this); |
| channel_ = NULL; |
| delete this; |
| return session; |
| } |
| |
| void TunnelSession::OnSessionState(BaseSession* session, |
| BaseSession::State state) { |
| LOG(LS_INFO) << "TunnelSession::OnSessionState(" |
| << rtc::nonnull( |
| rtc::FindLabel(state, SESSION_STATES), "Unknown") |
| << ")"; |
| ASSERT(session == session_); |
| |
| switch (state) { |
| case Session::STATE_RECEIVEDINITIATE: |
| OnInitiate(); |
| break; |
| case Session::STATE_SENTACCEPT: |
| case Session::STATE_RECEIVEDACCEPT: |
| OnAccept(); |
| break; |
| case Session::STATE_SENTTERMINATE: |
| case Session::STATE_RECEIVEDTERMINATE: |
| OnTerminate(); |
| break; |
| case Session::STATE_DEINIT: |
| // ReleaseSession should have been called before this. |
| ASSERT(false); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void TunnelSession::OnInitiate() { |
| ASSERT(client_ != NULL); |
| ASSERT(session_ != NULL); |
| client_->OnIncomingTunnel(buzz::Jid(session_->remote_name()), session_); |
| } |
| |
| void TunnelSession::OnAccept() { |
| ASSERT(channel_ != NULL); |
| const ContentInfo* content = |
| session_->remote_description()->FirstContentByType(NS_TUNNEL); |
| ASSERT(content != NULL); |
| VERIFY(channel_->Connect( |
| content->name, "tcp", ICE_CANDIDATE_COMPONENT_DEFAULT)); |
| } |
| |
| void TunnelSession::OnTerminate() { |
| ASSERT(channel_ != NULL); |
| channel_->OnSessionTerminate(session_); |
| } |
| |
| void TunnelSession::OnChannelClosed(PseudoTcpChannel* channel) { |
| ASSERT(channel_ == channel); |
| ASSERT(session_ != NULL); |
| session_->Terminate(); |
| } |
| |
| /////////////////////////////////////////////////////////////////////////////// |
| |
| } // namespace cricket |