summaryrefslogtreecommitdiffstats
path: root/remoting/jingle_glue
diff options
context:
space:
mode:
Diffstat (limited to 'remoting/jingle_glue')
-rw-r--r--remoting/jingle_glue/iq_request.cc79
-rw-r--r--remoting/jingle_glue/iq_request.h65
-rw-r--r--remoting/jingle_glue/iq_request_unittest.cc40
-rw-r--r--remoting/jingle_glue/jingle_channel.cc210
-rw-r--r--remoting/jingle_glue/jingle_channel.h158
-rw-r--r--remoting/jingle_glue/jingle_channel_unittest.cc146
-rw-r--r--remoting/jingle_glue/jingle_client.cc227
-rw-r--r--remoting/jingle_glue/jingle_client.h139
-rw-r--r--remoting/jingle_glue/jingle_info_task.cc132
-rw-r--r--remoting/jingle_glue/jingle_info_task.h45
-rw-r--r--remoting/jingle_glue/jingle_test_client.cc159
-rw-r--r--remoting/jingle_glue/jingle_thread.cc80
-rw-r--r--remoting/jingle_glue/jingle_thread.h70
-rw-r--r--remoting/jingle_glue/jingle_thread_unittest.cc27
-rw-r--r--remoting/jingle_glue/mock_objects.h26
-rw-r--r--remoting/jingle_glue/relay_port_allocator.cc29
-rw-r--r--remoting/jingle_glue/relay_port_allocator.h38
17 files changed, 1670 insertions, 0 deletions
diff --git a/remoting/jingle_glue/iq_request.cc b/remoting/jingle_glue/iq_request.cc
new file mode 100644
index 0000000..b9fd6bf
--- /dev/null
+++ b/remoting/jingle_glue/iq_request.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/iq_request.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/scoped_ptr.h"
+#include "remoting/jingle_glue/jingle_client.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace remoting {
+
+IqRequest::IqRequest(JingleClient* jingle_client)
+ : jingle_client_(jingle_client),
+ cookie_(NULL) {
+ DCHECK(jingle_client_ != NULL);
+ DCHECK(MessageLoop::current() == jingle_client_->message_loop());
+}
+
+IqRequest::~IqRequest() {
+ DCHECK(MessageLoop::current() == jingle_client_->message_loop());
+ Unregister();
+}
+
+void IqRequest::SendIq(const std::string& type,
+ const std::string& addressee,
+ buzz::XmlElement* iq_body) {
+ DCHECK(MessageLoop::current() == jingle_client_->message_loop());
+
+ // Unregister the handler if it is already registered.
+ Unregister();
+
+ DCHECK(type.length() > 0);
+ DCHECK(addressee.length() > 0);
+
+ buzz::XmppClient* xmpp_client = jingle_client_->xmpp_client();
+ DCHECK(xmpp_client); // Expect that connection is active.
+
+ scoped_ptr<buzz::XmlElement> stanza(MakeIqStanza(type, addressee, iq_body,
+ xmpp_client->NextId()));
+
+ xmpp_client->engine()->SendIq(stanza.get(), this, &cookie_);
+}
+
+// static
+buzz::XmlElement* IqRequest::MakeIqStanza(const std::string& type,
+ const std::string& addressee,
+ buzz::XmlElement* iq_body,
+ const std::string& id) {
+ buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_IQ);
+ stanza->AddAttr(buzz::QN_TYPE, type);
+ stanza->AddAttr(buzz::QN_TO, addressee);
+ stanza->AddAttr(buzz::QN_ID, id);
+ stanza->AddElement(iq_body);
+ return stanza;
+}
+
+void IqRequest::Unregister() {
+ if (cookie_) {
+ buzz::XmppClient* xmpp_client = jingle_client_->xmpp_client();
+ // No need to unregister the handler if the client has been destroyed.
+ if (xmpp_client) {
+ xmpp_client->engine()->RemoveIqHandler(cookie_, NULL);
+ }
+ cookie_ = NULL;
+ }
+}
+
+void IqRequest::IqResponse(buzz::XmppIqCookie cookie,
+ const buzz::XmlElement* stanza) {
+ if (callback_.get() != NULL) {
+ callback_->Run(stanza);
+ }
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/iq_request.h b/remoting/jingle_glue/iq_request.h
new file mode 100644
index 0000000..1e9e764
--- /dev/null
+++ b/remoting/jingle_glue/iq_request.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_IQ_REQUEST_H_
+#define REMOTING_JINGLE_GLUE_IQ_REQUEST_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "talk/xmpp/xmppengine.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace remoting {
+
+class JingleClient;
+
+// IqRequest class can be used to send an IQ stanza and then receive reply
+// stanza for that request. It sends outgoing stanza when SendIq() is called,
+// after that it forwards incoming reply stanza to the callback set with
+// set_callback(). If multiple IQ stanzas are send with SendIq() then only reply
+// to the last one will be received.
+// The class must be used on the jingle thread only.
+// TODO(sergeyu): Implement unittests for this class.
+class IqRequest : private buzz::XmppIqHandler {
+ public:
+ typedef Callback1<const buzz::XmlElement*>::Type ReplyCallback;
+
+ explicit IqRequest(JingleClient* jingle_client);
+ virtual ~IqRequest();
+
+ // Sends stanza of type |type| to |addressee|. |iq_body| contains body of
+ // the stanza. Ownership of |iq_body| is transfered to IqRequest. Must
+ // be called on the jingle thread.
+ void SendIq(const std::string& type, const std::string& addressee,
+ buzz::XmlElement* iq_body);
+
+ // Sets callback that is called when reply stanza is received. Callback
+ // is called on the jingle thread.
+ void set_callback(ReplyCallback* callback) {
+ callback_.reset(callback);
+ }
+
+ private:
+ FRIEND_TEST(IqRequestTest, MakeIqStanza);
+
+ // XmppIqHandler interface.
+ virtual void IqResponse(buzz::XmppIqCookie cookie,
+ const buzz::XmlElement* stanza);
+
+ static buzz::XmlElement* MakeIqStanza(const std::string& type,
+ const std::string& addressee,
+ buzz::XmlElement* iq_body,
+ const std::string& id);
+
+ void Unregister();
+
+ scoped_refptr<JingleClient> jingle_client_;
+ buzz::XmppIqCookie cookie_;
+ scoped_ptr<ReplyCallback> callback_;
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_IQ_REQUEST_H_
diff --git a/remoting/jingle_glue/iq_request_unittest.cc b/remoting/jingle_glue/iq_request_unittest.cc
new file mode 100644
index 0000000..88a6033
--- /dev/null
+++ b/remoting/jingle_glue/iq_request_unittest.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/ref_counted.h"
+#include "base/string_util.h"
+#include "media/base/data_buffer.h"
+#include "remoting/jingle_glue/iq_request.h"
+#include "talk/xmllite/xmlelement.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+TEST(IqRequestTest, MakeIqStanza) {
+ const char* kMessageId = "0";
+ const char* kNamespace = "chromium:testns";
+ const char* kNamespacePrefix = "tes";
+ const char* kBodyTag = "test";
+ const char* kType = "get";
+ const char* kTo = "user@domain.com";
+
+ std::string expected_xml_string =
+ StringPrintf(
+ "<cli:iq type=\"%s\" to=\"%s\" id=\"%s\" "
+ "xmlns:cli=\"jabber:client\">"
+ "<%s:%s xmlns:%s=\"%s\"/>"
+ "</cli:iq>",
+ kType, kTo, kMessageId, kNamespacePrefix, kBodyTag,
+ kNamespacePrefix, kNamespace);
+
+ buzz::XmlElement* iq_body =
+ new buzz::XmlElement(buzz::QName(kNamespace, kBodyTag));
+ scoped_ptr<buzz::XmlElement> stanza(
+ IqRequest::MakeIqStanza(kType, kTo, iq_body, kMessageId));
+
+ EXPECT_EQ(expected_xml_string, stanza->Str());
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_channel.cc b/remoting/jingle_glue/jingle_channel.cc
new file mode 100644
index 0000000..8111985
--- /dev/null
+++ b/remoting/jingle_glue/jingle_channel.cc
@@ -0,0 +1,210 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/jingle_channel.h"
+
+#include "base/lock.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/waitable_event.h"
+#include "media/base/data_buffer.h"
+#include "remoting/jingle_glue/jingle_thread.h"
+#include "talk/base/stream.h"
+
+using media::DataBuffer;
+
+namespace remoting {
+
+const size_t kReadBufferSize = 4096;
+
+JingleChannel::JingleChannel(Callback* callback)
+ : state_(INITIALIZING),
+ event_handler_(this),
+ callback_(callback),
+ write_buffer_size_(0),
+ current_write_buf_pos_(0) {
+ DCHECK(callback_ != NULL);
+}
+
+// This constructor is only used in unit test.
+JingleChannel::JingleChannel()
+ : state_(CLOSED),
+ write_buffer_size_(0),
+ current_write_buf_pos_(0) {
+}
+
+JingleChannel::~JingleChannel() {
+ DCHECK(state_ == CLOSED);
+}
+
+void JingleChannel::Init(JingleThread* thread,
+ talk_base::StreamInterface* stream,
+ const std::string& jid) {
+ thread_ = thread;
+ stream_.reset(stream);
+ stream_->SignalEvent.connect(&event_handler_, &EventHandler::OnStreamEvent);
+
+ // Initialize |state_|.
+ switch (stream->GetState()) {
+ case talk_base::SS_CLOSED:
+ SetState(CLOSED);
+ break;
+ case talk_base::SS_OPENING:
+ SetState(CONNECTING);
+ break;
+ case talk_base::SS_OPEN:
+ SetState(OPEN);
+ // Try to read in case there is something in the stream.
+ thread_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoRead));
+ break;
+ default:
+ NOTREACHED();
+ }
+
+ jid_ = jid;
+}
+
+void JingleChannel::Write(scoped_refptr<DataBuffer> data) {
+ // Discard empty packets.
+ if (data->GetDataSize() != 0) {
+ AutoLock auto_lock(write_lock_);
+ write_queue_.push_back(data);
+ write_buffer_size_ += data->GetDataSize();
+ // Post event so that the data gets written in the tunnel thread.
+ thread_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoWrite));
+ }
+}
+
+void JingleChannel::DoRead() {
+ while (true) {
+ size_t bytes_to_read;
+ if (stream_->GetAvailable(&bytes_to_read)) {
+ // Return immediately if we know there is nothing to read.
+ if (bytes_to_read == 0)
+ return;
+ } else {
+ // Try to read kReadBufferSize if the stream doesn't support
+ // GetAvailable().
+ bytes_to_read = kReadBufferSize;
+ }
+
+ scoped_refptr<DataBuffer> buffer(
+ new DataBuffer(new uint8[bytes_to_read], kReadBufferSize));
+ size_t bytes_read;
+ int error;
+ talk_base::StreamResult result = stream_->Read(
+ buffer->GetWritableData(), bytes_to_read, &bytes_read, &error);
+ switch (result) {
+ case talk_base::SR_SUCCESS: {
+ DCHECK(bytes_read > 0);
+ buffer->SetDataSize(bytes_read);
+ callback_->OnPacketReceived(this, buffer);
+ break;
+ }
+ case talk_base::SR_BLOCK: {
+ return;
+ }
+ case talk_base::SR_EOS: {
+ SetState(CLOSED);
+ return;
+ }
+ case talk_base::SR_ERROR: {
+ SetState(FAILED);
+ return;
+ }
+ }
+ }
+}
+
+void JingleChannel::DoWrite() {
+ while (true) {
+ if (!current_write_buf_) {
+ AutoLock auto_lock(write_lock_);
+ if (write_queue_.empty())
+ break;
+ current_write_buf_ = write_queue_.front();
+ current_write_buf_pos_ = 0;
+ write_queue_.pop_front();
+ }
+
+ size_t bytes_written;
+ int error;
+ talk_base::StreamResult result = stream_->Write(
+ current_write_buf_->GetData() + current_write_buf_pos_,
+ current_write_buf_->GetDataSize() - current_write_buf_pos_,
+ &bytes_written, &error);
+ switch (result) {
+ case talk_base::SR_SUCCESS: {
+ current_write_buf_pos_ += bytes_written;
+ if (current_write_buf_pos_ >= current_write_buf_->GetDataSize())
+ current_write_buf_ = NULL;
+ {
+ AutoLock auto_lock(write_lock_);
+ write_buffer_size_ -= bytes_written;
+ }
+ break;
+ }
+ case talk_base::SR_BLOCK: {
+ return;
+ }
+ case talk_base::SR_EOS: {
+ SetState(CLOSED);
+ return;
+ }
+ case talk_base::SR_ERROR: {
+ SetState(FAILED);
+ return;
+ }
+ }
+ }
+}
+
+void JingleChannel::OnStreamEvent(talk_base::StreamInterface* stream,
+ int events, int error) {
+ if (events & talk_base::SE_OPEN) {
+ SetState(OPEN);
+ }
+
+ if (state_ == OPEN && (events & talk_base::SE_WRITE)) {
+ DoWrite();
+ }
+
+ if (state_ == OPEN && (events & talk_base::SE_READ)) {
+ DoRead();
+ }
+
+ if (events & talk_base::SE_CLOSE) {
+ SetState(CLOSED);
+ }
+}
+
+void JingleChannel::SetState(State state) {
+ if (state == state_)
+ return;
+ state_ = state;
+ callback_->OnStateChange(this, state);
+}
+
+void JingleChannel::Close() {
+ base::WaitableEvent event(true, false);
+ thread_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleChannel::DoClose, &event));
+ event.Wait();
+}
+
+void JingleChannel::DoClose(base::WaitableEvent* done_event) {
+ if (stream_.get())
+ stream_->Close();
+ SetState(CLOSED);
+ done_event->Signal();
+}
+
+size_t JingleChannel::write_buffer_size() {
+ AutoLock auto_lock(write_lock_);
+ return write_buffer_size_;
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_channel.h b/remoting/jingle_glue/jingle_channel.h
new file mode 100644
index 0000000..e3f5e51
--- /dev/null
+++ b/remoting/jingle_glue/jingle_channel.h
@@ -0,0 +1,158 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_JINGLE_CHANNEL_H_
+#define REMOTING_JINGLE_GLUE_JINGLE_CHANNEL_H_
+
+#include <deque>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/condition_variable.h"
+#include "base/lock.h"
+#include "base/ref_counted.h"
+#include "base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+namespace base {
+class WaitableEvent;
+} // namespace base
+
+namespace talk_base {
+class StreamInterface;
+} // namespace talk_base
+
+namespace media {
+class Buffer;
+class DataBuffer;
+} // namespace media
+
+namespace remoting {
+class JingleThread;
+
+class JingleChannel : public base::RefCountedThreadSafe<JingleChannel> {
+ public:
+ enum State {
+ INITIALIZING,
+ CONNECTING,
+ OPEN,
+ CLOSED,
+ FAILED,
+ };
+
+ class Callback {
+ public:
+ virtual ~Callback() {}
+
+ // Called when state of the connection is changed.
+ virtual void OnStateChange(JingleChannel* channel, State state) = 0;
+
+ // Called when a new packet is received.
+ virtual void OnPacketReceived(JingleChannel* channel,
+ scoped_refptr<media::DataBuffer> data) = 0;
+ };
+
+ virtual ~JingleChannel();
+
+ // Puts data to the write buffer.
+ virtual void Write(scoped_refptr<media::DataBuffer> data);
+
+ // Closes the tunnel.
+ virtual void Close();
+
+ // Current state of the tunnel.
+ State state() { return state_; }
+
+ // JID of the other end of the channel.
+ const std::string& jid() { return jid_; }
+
+ // Number of bytes currently stored in the write buffer.
+ size_t write_buffer_size();
+
+ protected:
+ // Needs access to constructor, Init().
+ friend class JingleClient;
+
+ // Constructor used by unit test only.
+ // TODO(hclam): Have to suppress warnnings in MSVC.
+ JingleChannel();
+
+ // Used by JingleClient to create an instance of the channel. |callback|
+ // must not be NULL.
+ JingleChannel(Callback* callback);
+
+ // Initialized the channel. Ownership of the |stream| is transfered to
+ // caller. Ownership of |thread| is not.
+ void Init(JingleThread* thread, talk_base::StreamInterface* stream,
+ const std::string& jid);
+ void SetState(State state);
+
+ JingleThread* thread_;
+ scoped_ptr<talk_base::StreamInterface> stream_;
+ State state_;
+
+ private:
+ FRIEND_TEST(JingleChannelTest, Init);
+ FRIEND_TEST(JingleChannelTest, Write);
+ FRIEND_TEST(JingleChannelTest, Read);
+ FRIEND_TEST(JingleChannelTest, Close);
+
+ typedef std::deque<scoped_refptr<media::DataBuffer> > DataQueue;
+
+ // Event handler for the stream. It passes stream events from the stream
+ // to JingleChannel.
+ class EventHandler : public sigslot::has_slots<> {
+ protected:
+ EventHandler(JingleChannel* channel)
+ : channel_(channel) { }
+
+ // Constructor used only by unit test.
+ EventHandler() : channel_(NULL) {}
+
+ void OnStreamEvent(talk_base::StreamInterface* stream,
+ int events, int error) {
+ channel_->OnStreamEvent(stream, events, error);
+ }
+ friend class JingleChannel;
+ private:
+ JingleChannel* channel_;
+ };
+ friend class EventHandler;
+
+ // Event handler for the stream.
+ void OnStreamEvent(talk_base::StreamInterface* stream,
+ int events, int error);
+
+ // Writes data from the buffer to the stream. Called
+ // from OnStreamEvent() in the jingle thread.
+ void DoWrite();
+
+ // Reads data from the stream and puts it to the read buffer.
+ // Called from OnStreamEvent() in the jingle thread.
+ void DoRead();
+
+ void DoClose(base::WaitableEvent* done_event);
+
+ Callback* callback_;
+ EventHandler event_handler_;
+ std::string jid_;
+
+ // Write buffer. |write_lock_| should be locked when accessing |write_queue_|
+ // and |write_buffer_size_|, but isn't neccessary for |current_write_buf_|.
+ // |current_write_buf_| is accessed only by the jingle thread.
+ // |write_buffer_size_| stores number of bytes currently in |write_queue_|
+ // and in |current_write_buf_|.
+ DataQueue write_queue_;
+ size_t write_buffer_size_;
+ Lock write_lock_;
+ scoped_refptr<media::DataBuffer> current_write_buf_;
+ size_t current_write_buf_pos_;
+
+ DISALLOW_COPY_AND_ASSIGN(JingleChannel);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_JINGLE_CHANNEL_H_
diff --git a/remoting/jingle_glue/jingle_channel_unittest.cc b/remoting/jingle_glue/jingle_channel_unittest.cc
new file mode 100644
index 0000000..1a6a01e
--- /dev/null
+++ b/remoting/jingle_glue/jingle_channel_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/ref_counted.h"
+#include "media/base/data_buffer.h"
+#include "remoting/jingle_glue/jingle_channel.h"
+#include "remoting/jingle_glue/jingle_thread.h"
+#include "talk/base/stream.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+using testing::Return;
+using testing::Mock;
+using testing::SetArgumentPointee;
+
+namespace remoting {
+
+namespace {
+const size_t kBufferSize = 100;
+} // namespace
+
+class MockCallback : public JingleChannel::Callback {
+ public:
+ MOCK_METHOD2(OnStateChange, void(JingleChannel*, JingleChannel::State));
+ MOCK_METHOD2(OnPacketReceived, void(JingleChannel*,
+ scoped_refptr<media::DataBuffer>));
+};
+
+class MockStream : public talk_base::StreamInterface {
+ public:
+ virtual ~MockStream() {}
+ MOCK_CONST_METHOD0(GetState, talk_base::StreamState ());
+
+ MOCK_METHOD4(Read, talk_base::StreamResult (void*, size_t,
+ size_t*, int*));
+ MOCK_METHOD4(Write, talk_base::StreamResult (const void*, size_t,
+ size_t*, int*));
+ MOCK_CONST_METHOD1(GetAvailable, bool (size_t *));
+ MOCK_METHOD0(Close, void ());
+
+ MOCK_METHOD3(PostEvent, void (talk_base::Thread*, int, int));
+ MOCK_METHOD2(PostEvent, void (int, int));
+};
+
+TEST(JingleChannelTest, Init) {
+ JingleThread thread;
+
+ MockStream *stream = new MockStream();
+ MockCallback callback;
+
+ EXPECT_CALL(*stream, GetState())
+ .Times(1)
+ .WillRepeatedly(Return(talk_base::SS_OPENING));
+
+ scoped_refptr<JingleChannel> channel = new JingleChannel(&callback);
+
+ EXPECT_CALL(callback, OnStateChange(channel.get(), JingleChannel::CONNECTING))
+ .Times(1);
+
+ thread.Start();
+
+ EXPECT_EQ(JingleChannel::INITIALIZING, channel->state());
+ channel->Init(&thread, stream, "user@domain.com");
+ EXPECT_EQ(JingleChannel::CONNECTING, channel->state());
+ channel->state_ = JingleChannel::CLOSED;
+
+ thread.Stop();
+}
+
+TEST(JingleChannelTest, Write) {
+ JingleThread thread;
+ MockStream* stream = new MockStream(); // Freed by the channel.
+ MockCallback callback;
+
+ scoped_refptr<media::DataBuffer> data = new media::DataBuffer(kBufferSize);
+ data->SetDataSize(kBufferSize);
+
+ EXPECT_CALL(*stream, Write(static_cast<const void*>(data->GetData()),
+ kBufferSize, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(kBufferSize),
+ Return(talk_base::SR_SUCCESS)));
+
+ scoped_refptr<JingleChannel> channel = new JingleChannel(&callback);
+
+ channel->thread_ = &thread;
+ channel->stream_.reset(stream);
+ channel->state_ = JingleChannel::OPEN;
+ thread.Start();
+ channel->Write(data);
+ thread.Stop();
+ channel->state_ = JingleChannel::CLOSED;
+}
+
+TEST(JingleChannelTest, Read) {
+ JingleThread thread;
+ MockStream* stream = new MockStream(); // Freed by the channel.
+ MockCallback callback;
+
+ scoped_refptr<media::DataBuffer> data = new media::DataBuffer(kBufferSize);
+ data->SetDataSize(kBufferSize);
+
+ scoped_refptr<JingleChannel> channel = new JingleChannel(&callback);
+
+ EXPECT_CALL(callback, OnPacketReceived(channel.get(), _))
+ .Times(1);
+
+ EXPECT_CALL(*stream, GetAvailable(_))
+ .WillOnce(DoAll(SetArgumentPointee<0>(kBufferSize),
+ Return(true)))
+ .WillOnce(DoAll(SetArgumentPointee<0>(0),
+ Return(true)));
+
+ EXPECT_CALL(*stream, Read(_, kBufferSize, _, _))
+ .WillOnce(DoAll(SetArgumentPointee<2>(kBufferSize),
+ Return(talk_base::SR_SUCCESS)));
+
+ channel->thread_ = &thread;
+ channel->stream_.reset(stream);
+ channel->state_ = JingleChannel::OPEN;
+ thread.Start();
+ channel->OnStreamEvent(stream, talk_base::SE_READ, 0);
+ thread.Stop();
+ channel->state_ = JingleChannel::CLOSED;
+}
+
+TEST(JingleChannelTest, Close) {
+ JingleThread thread;
+ MockStream* stream = new MockStream(); // Freed by the channel.
+ MockCallback callback;
+
+ EXPECT_CALL(*stream, Close())
+ .Times(1);
+
+ scoped_refptr<JingleChannel> channel = new JingleChannel(&callback);
+
+ channel->thread_ = &thread;
+ channel->stream_.reset(stream);
+ channel->state_ = JingleChannel::OPEN;
+ thread.Start();
+ channel->Close();
+ thread.Stop();
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_client.cc b/remoting/jingle_glue/jingle_client.cc
new file mode 100644
index 0000000..66867f5
--- /dev/null
+++ b/remoting/jingle_glue/jingle_client.cc
@@ -0,0 +1,227 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/jingle_client.h"
+
+#include "base/logging.h"
+#include "base/waitable_event.h"
+#include "base/message_loop.h"
+#include "chrome/common/net/notifier/communicator/xmpp_socket_adapter.h"
+#include "remoting/jingle_glue/jingle_thread.h"
+#include "remoting/jingle_glue/relay_port_allocator.h"
+#include "talk/base/asyncsocket.h"
+#include "talk/base/ssladapter.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#ifdef USE_SSL_TUNNEL
+#include "talk/session/tunnel/securetunnelsessionclient.h"
+#endif
+#include "talk/session/tunnel/tunnelsessionclient.h"
+
+namespace remoting {
+
+JingleClient::JingleClient()
+ : callback_(NULL),
+ state_(START) { }
+
+JingleClient::~JingleClient() {
+ // JingleClient can be destroyed only after it's closed.
+ DCHECK(state_ == CLOSED);
+}
+
+void JingleClient::Init(const std::string& username,
+ const std::string& password,
+ Callback* callback) {
+ DCHECK(username != "");
+ DCHECK(callback != NULL);
+ DCHECK(thread_ == NULL); // Init() can be called only once.
+
+ callback_ = callback;
+
+ username_ = username;
+ password_ = password;
+
+ thread_.reset(new JingleThread());
+ thread_->Start();
+ thread_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleClient::DoInitialize));
+}
+
+class JingleClient::ConnectRequest {
+ public:
+ ConnectRequest()
+ : completed_event_(true, false) { }
+
+ JingleChannel* Wait() {
+ completed_event_.Wait();
+ return channel_;
+ };
+
+ void Done(JingleChannel* channel) {
+ channel_ = channel;
+ completed_event_.Signal();
+ };
+
+ private:
+ base::WaitableEvent completed_event_;
+ JingleChannel* channel_;
+};
+
+JingleChannel* JingleClient::Connect(const std::string& host_jid,
+ JingleChannel::Callback* callback) {
+ ConnectRequest request;
+ thread_->message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleClient::DoConnect,
+ &request, host_jid, callback));
+ return request.Wait();
+}
+
+void JingleClient::DoConnect(ConnectRequest* request,
+ const std::string& host_jid,
+ JingleChannel::Callback* callback) {
+ talk_base::StreamInterface* stream =
+ tunnel_session_client_->CreateTunnel(buzz::Jid(host_jid), "");
+ DCHECK(stream != NULL);
+
+ JingleChannel* channel = new JingleChannel(callback);
+ channel->Init(thread_.get(), stream, host_jid);
+ request->Done(channel);
+}
+
+void JingleClient::Close() {
+ DCHECK(thread_ != NULL); // Close() be called only after Init().
+ message_loop()->PostTask(
+ FROM_HERE, NewRunnableMethod(this, &JingleClient::DoClose));
+ thread_->Stop();
+ thread_.reset(NULL);
+}
+
+void JingleClient::DoClose() {
+ client_->Disconnect();
+ // Client is deleted by TaskRunner.
+ client_ = NULL;
+ tunnel_session_client_.reset();
+ port_allocator_.reset();
+ session_manager_.reset();
+ network_manager_.reset();
+ UpdateState(CLOSED);
+}
+
+void JingleClient::DoInitialize() {
+ buzz::Jid login_jid(username_);
+ talk_base::InsecureCryptStringImpl password;
+ password.password() = password_;
+
+ buzz::XmppClientSettings xcs;
+ xcs.set_user(login_jid.node());
+ xcs.set_host(login_jid.domain());
+ xcs.set_resource("chromoting");
+ xcs.set_use_tls(true);
+ xcs.set_pass(talk_base::CryptString(password));
+ xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
+
+ client_ = new buzz::XmppClient(thread_->task_pump());
+ client_->SignalStateChange.connect(
+ this, &JingleClient::OnConnectionStateChanged);
+
+ buzz::AsyncSocket* socket =
+ new notifier::XmppSocketAdapter(xcs, false);
+
+ client_->Connect(xcs, "", socket, NULL);
+ client_->Start();
+
+ network_manager_.reset(new talk_base::NetworkManager());
+
+ RelayPortAllocator* port_allocator =
+ new RelayPortAllocator(network_manager_.get(), "transp2");
+ port_allocator_.reset(port_allocator);
+ port_allocator->SetJingleInfo(client_);
+
+ session_manager_.reset(new cricket::SessionManager(port_allocator_.get()));
+#ifdef USE_SSL_TUNNEL
+ cricket::SecureTunnelSessionClient* session_client =
+ new cricket::SecureTunnelSessionClient(client_->jid(),
+ session_manager_.get());
+ if (!session_client->GenerateIdentity())
+ return false;
+ tunnel_session_client_.reset(session_client);
+#else // !USE_SSL_TUNNEL
+ tunnel_session_client_.reset(
+ new cricket::TunnelSessionClient(client_->jid(),
+ session_manager_.get()));
+#endif // USE_SSL_TUNNEL
+
+ receiver_ = new cricket::SessionManagerTask(client_, session_manager_.get());
+ receiver_->EnableOutgoingMessages();
+ receiver_->Start();
+
+ tunnel_session_client_->SignalIncomingTunnel.connect(
+ this, &JingleClient::OnIncomingTunnel);
+}
+
+std::string JingleClient::GetFullJid() {
+ AutoLock auto_lock(full_jid_lock_);
+ return full_jid_;
+}
+
+MessageLoop* JingleClient::message_loop() {
+ if (thread_ == NULL) {
+ return NULL;
+ }
+ return thread_->message_loop();
+}
+
+void JingleClient::OnConnectionStateChanged(buzz::XmppEngine::State state) {
+ switch (state) {
+ case buzz::XmppEngine::STATE_START:
+ UpdateState(START);
+ break;
+ case buzz::XmppEngine::STATE_OPENING:
+ UpdateState(CONNECTING);
+ break;
+ case buzz::XmppEngine::STATE_OPEN:
+ {
+ AutoLock auto_lock(full_jid_lock_);
+ full_jid_ = client_->jid().Str();
+ }
+ UpdateState(CONNECTED);
+ break;
+ case buzz::XmppEngine::STATE_CLOSED:
+ UpdateState(CLOSED);
+ break;
+ }
+}
+
+void JingleClient::OnIncomingTunnel(
+ cricket::TunnelSessionClient* client, buzz::Jid jid,
+ std::string description, cricket::Session* session) {
+ // Decline connection if we don't have callback.
+ if (!callback_) {
+ client->DeclineTunnel(session);
+ return;
+ }
+
+ JingleChannel::Callback* channel_callback;
+ if (callback_->OnAcceptConnection(this, jid.Str(), &channel_callback)) {
+ DCHECK(channel_callback != NULL);
+ talk_base::StreamInterface* stream =
+ client->AcceptTunnel(session);
+ scoped_refptr<JingleChannel> channel(new JingleChannel(channel_callback));
+ channel->Init(thread_.get(), stream, jid.Str());
+ callback_->OnNewConnection(this, channel);
+ } else {
+ client->DeclineTunnel(session);
+ return;
+ }
+}
+
+void JingleClient::UpdateState(State new_state) {
+ if (new_state != state_) {
+ state_ = new_state;
+ if (callback_)
+ callback_->OnStateChange(this, new_state);
+ }
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_client.h b/remoting/jingle_glue/jingle_client.h
new file mode 100644
index 0000000..56d97e1
--- /dev/null
+++ b/remoting/jingle_glue/jingle_client.h
@@ -0,0 +1,139 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_
+#define REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_
+
+#include <string>
+
+#include "remoting/jingle_glue/jingle_channel.h"
+#include "talk/xmpp/xmppclient.h"
+
+class MessageLoop;
+
+namespace talk_base {
+class NetworkManager;
+} // namespace talk_base
+
+namespace cricket {
+class BasicPortAllocator;
+class SessionManager;
+class TunnelSessionClient;
+class SessionManagerTask;
+class Session;
+} // namespace cricket
+
+namespace remoting {
+
+class JingleClient : public base::RefCountedThreadSafe<JingleClient>,
+ public sigslot::has_slots<> {
+ public:
+ enum State {
+ START, // Initial state.
+ CONNECTING,
+ CONNECTED,
+ CLOSED,
+ };
+
+ class Callback {
+ public:
+ virtual ~Callback() {}
+
+ // Called when state of the connection is changed.
+ virtual void OnStateChange(JingleClient* client, State state) = 0;
+
+ // Called when a client attempts to connect to the machine. If the
+ // connection should be accepted, must return true and must set
+ // channel_callback to the callback for the new channel.
+ virtual bool OnAcceptConnection(
+ JingleClient* client, const std::string& jid,
+ JingleChannel::Callback** channel_callback) = 0;
+
+ // Called when a new client connects to the host. Ownership of the |channel|
+ // is transfered to the callee.
+ virtual void OnNewConnection(JingleClient* client,
+ scoped_refptr<JingleChannel> channel) = 0;
+ };
+
+ JingleClient();
+ virtual ~JingleClient();
+
+ // Starts jingle thread and XMPP connection inialization. Must be called
+ // only once. message_loop() is guaranteed to exist after this method returns,
+ // but the connection may not be open yet. |callback| specifies callback
+ // object for the client and must not be NULL.
+ // TODO(sergeyu): Replace password with a token.
+ void Init(const std::string& username, const std::string& password,
+ Callback* callback);
+
+ // Creates new JingleChannel connected to the host with the specified jid.
+ // The result is returned immediately but the channel fails if the host
+ // rejects connection. |host_jid| must be a full jid (includes resource ID).
+ // Ownership of the result is transfered to the caller. The channel must
+ // be closed/destroyed before JingleClient is destroyed.
+ JingleChannel* Connect(const std::string& host_jid,
+ JingleChannel::Callback* callback);
+
+ // Closes XMPP connection and stops the thread. Must be called before the
+ // object is destroyed.
+ void Close();
+
+ // Returns JID with resource ID. Empty string is returned if full JID is not
+ // known yet, i.e. authentication hasn't finished.
+ std::string GetFullJid();
+
+ // Current state of the client.
+ State state() { return state_; }
+
+ // Returns XmppClient object for the xmpp connection or NULL if not connected.
+ buzz::XmppClient* xmpp_client() { return client_; }
+
+ // Message loop for the jingle thread or NULL if the thread is not started.
+ MessageLoop* message_loop();
+
+ private:
+ // Used by Connect().
+ class ConnectRequest;
+
+ void OnConnectionStateChanged(buzz::XmppEngine::State state);
+
+ void OnIncomingTunnel(cricket::TunnelSessionClient* client, buzz::Jid jid,
+ std::string description, cricket::Session* session);
+
+ void DoInitialize();
+
+ // Used by Connect().
+ void DoConnect(ConnectRequest* request,
+ const std::string& host_jid,
+ JingleChannel::Callback* callback);
+
+ // Used by Close().
+ void DoClose();
+
+ // Updates current state of the connection. Must be called only in
+ // the jingle thread.
+ void UpdateState(State new_state);
+
+ buzz::XmppClient* client_;
+ scoped_ptr<JingleThread> thread_;
+ State state_;
+ Callback* callback_;
+
+ std::string username_;
+ std::string password_;
+ Lock full_jid_lock_;
+ std::string full_jid_;
+
+ scoped_ptr<talk_base::NetworkManager> network_manager_;
+ scoped_ptr<cricket::BasicPortAllocator> port_allocator_;
+ scoped_ptr<cricket::SessionManager> session_manager_;
+ scoped_ptr<cricket::TunnelSessionClient> tunnel_session_client_;
+ cricket::SessionManagerTask* receiver_;
+
+ DISALLOW_COPY_AND_ASSIGN(JingleClient);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_JINGLE_CLIENT_H_
diff --git a/remoting/jingle_glue/jingle_info_task.cc b/remoting/jingle_glue/jingle_info_task.cc
new file mode 100644
index 0000000..c2e6525
--- /dev/null
+++ b/remoting/jingle_glue/jingle_info_task.cc
@@ -0,0 +1,132 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/jingle_info_task.h"
+
+#include "base/scoped_ptr.h"
+#include "talk/base/socketaddress.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace remoting {
+
+// This code is a copy of googleclient/talk/app/jingleinfotask.cc .
+
+class JingleInfoTask::JingleInfoGetTask : public XmppTask {
+ public:
+ explicit JingleInfoGetTask(talk_base::TaskParent* parent) :
+ XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+ done_(false) { }
+
+ virtual int ProcessStart() {
+ // Set jingle info query IQ stanza.
+ scoped_ptr<buzz::XmlElement> get_iq(
+ MakeIq(buzz::STR_GET, buzz::JID_EMPTY, task_id()));
+ get_iq->AddElement(new buzz::XmlElement(buzz::QN_JINGLE_INFO_QUERY, true));
+ if (SendStanza(get_iq.get()) != buzz::XMPP_RETURN_OK) {
+ return STATE_ERROR;
+ }
+ return STATE_RESPONSE;
+ }
+
+ virtual int ProcessResponse() {
+ if (done_) {
+ return STATE_DONE;
+ }
+ return STATE_BLOCKED;
+ }
+
+ protected:
+ virtual bool HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, buzz::JID_EMPTY, task_id())) {
+ return false;
+ }
+
+ if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_RESULT) {
+ return false;
+ }
+
+ // Queue the stanza with the parent so these don't get handled out of order.
+ JingleInfoTask* parent = static_cast<JingleInfoTask*>(GetParent());
+ parent->QueueStanza(stanza);
+
+ // Wake ourselves so we can go into the done state.
+ done_ = true;
+ Wake();
+ return true;
+ }
+
+ bool done_;
+};
+
+
+void JingleInfoTask::RefreshJingleInfoNow() {
+ JingleInfoGetTask* get_task = new JingleInfoGetTask(this);
+ get_task->Start();
+}
+
+bool JingleInfoTask::HandleStanza(const buzz::XmlElement* stanza) {
+ if (!MatchRequestIq(stanza, "set", buzz::QN_JINGLE_INFO_QUERY)) {
+ return false;
+ }
+
+ // Only respect relay push from the server.
+ buzz::Jid from(stanza->Attr(buzz::QN_FROM));
+ if (from != buzz::JID_EMPTY &&
+ !from.BareEquals(GetClient()->jid()) &&
+ from != buzz::Jid(GetClient()->jid().domain())) {
+ return false;
+ }
+
+ QueueStanza(stanza);
+ return true;
+}
+
+int JingleInfoTask::ProcessStart() {
+ std::vector<std::string> relay_hosts;
+ std::vector<talk_base::SocketAddress> stun_hosts;
+ std::string relay_token;
+ const buzz::XmlElement* stanza = NextStanza();
+ if (stanza == NULL) {
+ return STATE_BLOCKED;
+ }
+ const buzz::XmlElement* query =
+ stanza->FirstNamed(buzz::QN_JINGLE_INFO_QUERY);
+ if (query == NULL) {
+ return STATE_START;
+ }
+ const buzz::XmlElement* stun = query->FirstNamed(buzz::QN_JINGLE_INFO_STUN);
+ if (stun) {
+ for (const buzz::XmlElement* server =
+ stun->FirstNamed(buzz::QN_JINGLE_INFO_SERVER);
+ server != NULL;
+ server = server->NextNamed(buzz::QN_JINGLE_INFO_SERVER)) {
+ std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST);
+ std::string port = server->Attr(buzz::QN_JINGLE_INFO_UDP);
+ if (host != buzz::STR_EMPTY && host != buzz::STR_EMPTY) {
+ // TODO(sergeyu): Avoid atoi() here.
+ stun_hosts.push_back(
+ talk_base::SocketAddress(host, atoi(port.c_str())));
+ }
+ }
+ }
+
+ const buzz::XmlElement* relay = query->FirstNamed(buzz::QN_JINGLE_INFO_RELAY);
+ if (relay) {
+ relay_token = relay->TextNamed(buzz::QN_JINGLE_INFO_TOKEN);
+ for (const buzz::XmlElement* server =
+ relay->FirstNamed(buzz::QN_JINGLE_INFO_SERVER);
+ server != NULL;
+ server = server->NextNamed(buzz::QN_JINGLE_INFO_SERVER)) {
+ std::string host = server->Attr(buzz::QN_JINGLE_INFO_HOST);
+ if (host != buzz::STR_EMPTY) {
+ relay_hosts.push_back(host);
+ }
+ }
+ }
+ SignalJingleInfo(relay_token, relay_hosts, stun_hosts);
+ return STATE_START;
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_info_task.h b/remoting/jingle_glue/jingle_info_task.h
new file mode 100644
index 0000000..f02e54e
--- /dev/null
+++ b/remoting/jingle_glue/jingle_info_task.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_JINGLE_INFO_TASK_H_
+#define REMOTING_JINGLE_GLUE_JINGLE_INFO_TASK_H_
+
+#include <vector>
+#include <string>
+
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/httpportallocator.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace remoting {
+
+// JingleInfoTask is used to discover addresses of jingle servers.
+// See http://code.google.com/apis/talk/jep_extensions/jingleinfo.html
+// for more details about the protocol.
+//
+// This is a copy of googleclient/talk/app/jingleinfotask.h .
+class JingleInfoTask : public buzz::XmppTask {
+ public:
+ explicit JingleInfoTask(talk_base::TaskParent* parent)
+ : XmppTask(parent, buzz::XmppEngine::HL_TYPE) {}
+
+ virtual int ProcessStart();
+ void RefreshJingleInfoNow();
+
+ sigslot::signal3<const std::string&,
+ const std::vector<std::string>&,
+ const std::vector<talk_base::SocketAddress>&>
+ SignalJingleInfo;
+
+ protected:
+ class JingleInfoGetTask;
+ friend class JingleInfoGetTask;
+
+ virtual bool HandleStanza(const buzz::XmlElement* stanza);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_JINGLE_INFO_TASK_H_
diff --git a/remoting/jingle_glue/jingle_test_client.cc b/remoting/jingle_glue/jingle_test_client.cc
new file mode 100644
index 0000000..38677fe
--- /dev/null
+++ b/remoting/jingle_glue/jingle_test_client.cc
@@ -0,0 +1,159 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "build/build_config.h"
+
+#if !defined(OS_WIN)
+extern "C" {
+#include <unistd.h>
+}
+#endif // !defined(OS_WIN)
+
+#include <iostream>
+#include <list>
+
+#include "base/at_exit.h"
+#include "media/base/data_buffer.h"
+#include "remoting/jingle_glue/jingle_channel.h"
+#include "remoting/jingle_glue/jingle_client.h"
+
+using remoting::JingleClient;
+using remoting::JingleChannel;
+
+void SetConsoleEcho(bool on) {
+#if defined(OS_WIN)
+ HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+ if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+ return;
+
+ DWORD mode;
+ if (!GetConsoleMode(hIn, &mode))
+ return;
+
+ if (on) {
+ mode = mode | ENABLE_ECHO_INPUT;
+ } else {
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ }
+
+ SetConsoleMode(hIn, mode);
+#else // defined(OS_WIN)
+ if (on)
+ system("stty echo");
+ else
+ system("stty -echo");
+#endif // !defined(OS_WIN)
+}
+
+class JingleTestClient : public JingleChannel::Callback,
+ public JingleClient::Callback {
+ public:
+ virtual ~JingleTestClient() {}
+
+ void Run(const std::string& username, const std::string& password,
+ const std::string& host_jid) {
+ client_ = new JingleClient();
+ client_->Init(username, password, this);
+
+ if (host_jid != "") {
+ scoped_refptr<JingleChannel> channel = client_->Connect(host_jid, this);
+ channels_.push_back(channel);
+ }
+
+ while (true) {
+ std::string line;
+ std::getline(std::cin, line);
+
+ {
+ AutoLock auto_lock(channels_lock_);
+
+ // Broadcast message to all clients.
+ for (ChannelsList::iterator it = channels_.begin();
+ it != channels_.end(); ++it) {
+ uint8* buf = new uint8[line.length()];
+ memcpy(buf, line.c_str(), line.length());
+ (*it)->Write(new media::DataBuffer(buf, line.length()));
+ }
+ }
+
+ if (line == "exit")
+ break;
+ }
+
+ while (!channels_.empty()) {
+ channels_.front()->Close();
+ channels_.pop_front();
+ }
+
+ client_->Close();
+ }
+
+ // JingleChannel::Callback interface.
+ void OnStateChange(JingleChannel* channel, JingleChannel::State state) {
+ LOG(INFO) << "State of " << channel->jid() << " changed to " << state;
+ }
+
+ void OnPacketReceived(JingleChannel* channel,
+ scoped_refptr<media::DataBuffer> buffer) {
+ std::string str(reinterpret_cast<const char*>(buffer->GetData()),
+ buffer->GetDataSize());
+ std::cout << "(" << channel->jid() << "): " << str << std::endl;
+ }
+
+ // JingleClient::Callback interface.
+ void OnStateChange(JingleClient* client, JingleClient::State state) {
+ if (state == JingleClient::CONNECTED) {
+ std::cerr << "Connected as " << client->GetFullJid() << std::endl;
+ } else if (state == JingleClient::CLOSED) {
+ std::cerr << "Connection closed" << std::endl;
+ }
+ }
+
+ bool OnAcceptConnection(JingleClient* client, const std::string& jid,
+ JingleChannel::Callback** callback) {
+ std::cerr << "Accepting new connection from " << jid << std::endl;
+ *callback = this;
+ return true;
+ }
+
+ void OnNewConnection(JingleClient* client,
+ scoped_refptr<JingleChannel> channel) {
+ std::cerr << "Connected to " << channel->jid() << std::endl;
+ AutoLock auto_lock(channels_lock_);
+ channels_.push_back(channel);
+ }
+
+ private:
+ typedef std::list<scoped_refptr<JingleChannel> > ChannelsList;
+
+ scoped_refptr<JingleClient> client_;
+ ChannelsList channels_;
+ Lock channels_lock_;
+};
+
+int main(int argc, char** argv) {
+ if (argc > 2)
+ std::cerr << "Usage: " << argv[0] << " [<host_jid>]" << std::endl;
+
+ base::AtExitManager exit_manager;
+
+ std::string host_jid = argc == 2 ? argv[1] : "";
+
+ std::string username;
+ std::cout << "JID: ";
+ std::cin >> username;
+
+ std::string password;
+ SetConsoleEcho(false);
+ std::cout << "Password: ";
+ std::cin >> password;
+ SetConsoleEcho(true);
+ std::cout << std::endl;
+
+ JingleTestClient client;
+
+ client.Run(username, password, host_jid);
+
+ return 0;
+}
diff --git a/remoting/jingle_glue/jingle_thread.cc b/remoting/jingle_glue/jingle_thread.cc
new file mode 100644
index 0000000..5090251
--- /dev/null
+++ b/remoting/jingle_glue/jingle_thread.cc
@@ -0,0 +1,80 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/jingle_thread.h"
+
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "talk/base/ssladapter.h"
+
+namespace remoting {
+
+const int kRunTasksMessageId = 1;
+
+TaskPump::TaskPump() {
+}
+
+void TaskPump::WakeTasks() {
+ talk_base::Thread::Current()->Post(this);
+}
+
+int64 TaskPump::CurrentTime() {
+ return static_cast<int64>(talk_base::Time());
+}
+
+void TaskPump::OnMessage(talk_base::Message* pmsg) {
+ RunTasks();
+}
+
+JingleThread::JingleThread()
+ : message_loop_(NULL),
+ task_pump_(NULL),
+ started_event_(true, false) { }
+
+JingleThread::~JingleThread() { }
+
+void JingleThread::Start() {
+ Thread::Start();
+ started_event_.Wait();
+}
+
+void JingleThread::Run() {
+ LOG(INFO) << "Started Jingle thread.";
+
+ MessageLoopForIO message_loop;
+ message_loop_ = &message_loop;
+
+ TaskPump task_pump;
+ task_pump_ = &task_pump;
+
+ // Signal after we've initialized |message_loop_| and |task_pump_|.
+ started_event_.Signal();
+
+ Post(this, kRunTasksMessageId);
+
+ Thread::Run();
+
+ message_loop.RunAllPending();
+
+ task_pump_ = NULL;
+ message_loop_ = NULL;
+
+ LOG(INFO) << "Jingle thread finished.";
+}
+
+// This method is called every 20ms to process tasks from |message_loop_|
+// on this thread.
+// TODO(sergeyu): Remove it when JingleThread moved to Chromium's base::Thread.
+void JingleThread::PumpAuxiliaryLoops() {
+ MessageLoop::current()->RunAllPending();
+
+ // Schedule next execution 20ms from now.
+ PostDelayed(20, this, kRunTasksMessageId);
+}
+
+void JingleThread::OnMessage(talk_base::Message* msg) {
+ PumpAuxiliaryLoops();
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/jingle_thread.h b/remoting/jingle_glue/jingle_thread.h
new file mode 100644
index 0000000..ecef1fb
--- /dev/null
+++ b/remoting/jingle_glue/jingle_thread.h
@@ -0,0 +1,70 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_JINGLE_THREAD_H
+#define REMOTING_JINGLE_GLUE_JINGLE_THREAD_H
+
+#include "base/tracked_objects.h"
+#include "base/waitable_event.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+
+class MessageLoop;
+
+namespace buzz {
+class XmppClient;
+}
+
+namespace remoting {
+
+class TaskPump : public talk_base::MessageHandler,
+ public talk_base::TaskRunner {
+ public:
+ TaskPump();
+
+ // TaskRunner methods.
+ void WakeTasks();
+ int64 CurrentTime();
+
+ // MessageHandler methods.
+ void OnMessage(talk_base::Message* pmsg);
+};
+
+// TODO(sergeyu): This class should be changed to inherit from Chromiums
+// base::Thread instead of libjingle's thread.
+class JingleThread : public talk_base::Thread,
+ private talk_base::MessageHandler {
+ public:
+ JingleThread();
+ virtual ~JingleThread();
+
+ void Start();
+
+ // Main function for the thread. Should not be called directly.
+ void Run();
+
+ // Returns Chromiums message loop for this thread.
+ // TODO(sergeyu): remove this methid when we use base::Thread insted of
+ // talk_base::Thread
+ MessageLoop* message_loop() { return message_loop_; }
+
+ // Returns task pump if the thread is running, otherwise NULL is returned.
+ TaskPump* task_pump() { return task_pump_; }
+
+ private:
+ virtual void OnMessage(talk_base::Message* msg);
+
+ void PumpAuxiliaryLoops();
+
+ TaskPump* task_pump_;
+ base::WaitableEvent started_event_;
+ MessageLoop* message_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(JingleThread);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_JINGLE_THREAD_H
diff --git a/remoting/jingle_glue/jingle_thread_unittest.cc b/remoting/jingle_glue/jingle_thread_unittest.cc
new file mode 100644
index 0000000..a29b69c
--- /dev/null
+++ b/remoting/jingle_glue/jingle_thread_unittest.cc
@@ -0,0 +1,27 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/message_loop.h"
+#include "remoting/jingle_glue/jingle_thread.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace remoting {
+
+class MockTask : public Task {
+ public:
+ MOCK_METHOD0(Run, void());
+};
+
+TEST(JingleThreadTest, PostTask) {
+ JingleThread thread;
+ MockTask* task = new MockTask();
+ EXPECT_CALL(*task, Run());
+
+ thread.Start();
+ thread.message_loop()->PostTask(FROM_HERE, task);
+ thread.Stop();
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/mock_objects.h b/remoting/jingle_glue/mock_objects.h
new file mode 100644
index 0000000..c1a1e26
--- /dev/null
+++ b/remoting/jingle_glue/mock_objects.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_MOCK_OBJECTS_H_
+#define REMOTING_JINGLE_GLUE_MOCK_OBJECTS_H_
+
+#include "remoting/jingle_glue/jingle_channel.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+namespace remoting {
+
+class MockJingleChannel : public JingleChannel {
+ public:
+ MockJingleChannel() {}
+
+ MOCK_METHOD1(Write, void(scoped_refptr<media::DataBuffer> data));
+ MOCK_METHOD0(Close, void());
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockJingleChannel);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_MOCK_OBJECTS_H_
diff --git a/remoting/jingle_glue/relay_port_allocator.cc b/remoting/jingle_glue/relay_port_allocator.cc
new file mode 100644
index 0000000..8b50dca
--- /dev/null
+++ b/remoting/jingle_glue/relay_port_allocator.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "remoting/jingle_glue/relay_port_allocator.h"
+
+#include "remoting/jingle_glue/jingle_info_task.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace remoting {
+
+void RelayPortAllocator::OnJingleInfo(
+ const std::string & token,
+ const std::vector<std::string> & relay_hosts,
+ const std::vector<talk_base::SocketAddress> & stun_hosts) {
+ this->SetRelayToken(token);
+ this->SetStunHosts(stun_hosts);
+ this->SetRelayHosts(relay_hosts);
+}
+
+void RelayPortAllocator::SetJingleInfo(buzz::XmppClient* client) {
+ // The JingleInfoTask is freed by the task-runner.
+ JingleInfoTask* jit = new JingleInfoTask(client);
+ jit->SignalJingleInfo.connect(this, &RelayPortAllocator::OnJingleInfo);
+ jit->Start();
+ jit->RefreshJingleInfoNow();
+}
+
+} // namespace remoting
diff --git a/remoting/jingle_glue/relay_port_allocator.h b/remoting/jingle_glue/relay_port_allocator.h
new file mode 100644
index 0000000..2651d3f3
--- /dev/null
+++ b/remoting/jingle_glue/relay_port_allocator.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef REMOTING_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_
+#define REMOTING_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_
+
+#include <string>
+
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/httpportallocator.h"
+
+namespace buzz {
+class XmppClient;
+} // namespace buzz
+
+namespace remoting {
+
+class RelayPortAllocator: public cricket::HttpPortAllocator,
+ public sigslot::has_slots<> {
+ public:
+ RelayPortAllocator(talk_base::NetworkManager* network_manager,
+ const std::string& user_agent):
+ cricket::HttpPortAllocator(network_manager, user_agent) { }
+
+ void OnJingleInfo(const std::string& token,
+ const std::vector<std::string>& relay_hosts,
+ const std::vector<talk_base::SocketAddress>& stun_hosts);
+
+ void SetJingleInfo(buzz::XmppClient* client);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(RelayPortAllocator);
+};
+
+} // namespace remoting
+
+#endif // REMOTING_JINGLE_GLUE_RELAY_PORT_ALLOCATOR_H_