Update to libjingle 0.6.0.
git-svn-id: http://libjingle.googlecode.com/svn/trunk@84 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/CHANGELOG b/CHANGELOG
index a6cfd9c..d5c6d59 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,5 +1,9 @@
Libjingle
+0.6.0 - Sep 13, 2011
+ - Add pub sub support
+ - Add unit tests
+
0.5.9 - Aug 31, 2011
- Add app/webrtc
- Add webrtcvoiceengine/webrtcvideoengine
diff --git a/talk/app/webrtc/peerconnectionfactory.cc b/talk/app/webrtc/peerconnectionfactory.cc
new file mode 100644
index 0000000..0930bde
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/app/webrtc/peerconnectionfactory.h"
+
+#include "talk/app/webrtc/peerconnection_proxy.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/session/phone/channelmanager.h"
+
+namespace webrtc {
+
+PeerConnectionFactory::PeerConnectionFactory(
+ cricket::PortAllocator* port_allocator,
+ cricket::MediaEngineInterface* media_engine,
+ cricket::DeviceManager* device_manager,
+ talk_base::Thread* worker_thread)
+ : initialized_(false),
+ port_allocator_(port_allocator),
+ channel_manager_(new cricket::ChannelManager(media_engine,
+ device_manager,
+ worker_thread)) {
+}
+
+PeerConnectionFactory::PeerConnectionFactory(
+ cricket::PortAllocator* port_allocator,
+ talk_base::Thread* worker_thread)
+ : initialized_(false),
+ port_allocator_(port_allocator),
+ channel_manager_(new cricket::ChannelManager(worker_thread)) {
+}
+
+PeerConnectionFactory::~PeerConnectionFactory() {
+}
+
+bool PeerConnectionFactory::Initialize() {
+ ASSERT(channel_manager_.get());
+ initialized_ = channel_manager_->Init();
+ return initialized_;
+}
+
+PeerConnection* PeerConnectionFactory::CreatePeerConnection(
+ talk_base::Thread* signaling_thread) {
+ PeerConnectionProxy* pc = NULL;
+ if (initialized_) {
+ pc = new PeerConnectionProxy(
+ port_allocator_.get(), channel_manager_.get(), signaling_thread);
+ if (!pc->Init()) {
+ LOG(LERROR) << "Error in initializing PeerConnection";
+ delete pc;
+ pc = NULL;
+ }
+ } else {
+ LOG(LERROR) << "PeerConnectionFactory is not initialize";
+ }
+ return pc;
+}
+
+} // namespace webrtc
diff --git a/talk/app/webrtc/peerconnectionfactory.h b/talk/app/webrtc/peerconnectionfactory.h
new file mode 100644
index 0000000..7c65e05
--- /dev/null
+++ b/talk/app/webrtc/peerconnectionfactory.h
@@ -0,0 +1,75 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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.
+ */
+
+#ifndef TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+#define TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+
+namespace cricket {
+class ChannelManager;
+class DeviceManagerInterface;
+class MediaEngineInterface;
+class PortAllocator;
+} // namespace cricket
+
+namespace talk_base {
+class SocketAddress;
+class Thread;
+} // namespace talk_base
+
+namespace webrtc {
+
+class PeerConnection;
+
+class PeerConnectionFactory {
+ public:
+ PeerConnectionFactory(cricket::PortAllocator* port_allocator,
+ cricket::MediaEngineInterface* media_engine,
+ cricket::DeviceManagerInterface* device_manager,
+ talk_base::Thread* worker_thread);
+ PeerConnectionFactory(cricket::PortAllocator* port_allocator,
+ talk_base::Thread* worker_thread);
+
+ virtual ~PeerConnectionFactory();
+ bool Initialize();
+
+ PeerConnection* CreatePeerConnection(talk_base::Thread* signaling_thread);
+
+ private:
+ bool initialized_;
+ talk_base::scoped_ptr<cricket::PortAllocator> port_allocator_;
+ talk_base::scoped_ptr<cricket::ChannelManager> channel_manager_;
+};
+
+} // namespace webrtc
+
+#endif // TALK_APP_WEBRTC_PEERCONNECTIONFACTORY_H_
+
diff --git a/talk/base/asynchttprequest_unittest.cc b/talk/base/asynchttprequest_unittest.cc
new file mode 100644
index 0000000..e8db1e0
--- /dev/null
+++ b/talk/base/asynchttprequest_unittest.cc
@@ -0,0 +1,191 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 <string>
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+static const SocketAddress kServerAddr("127.0.0.1", 0);
+static const SocketAddress kServerHostnameAddr("localhost", 0);
+static const char kServerGetPath[] = "/get";
+static const char kServerPostPath[] = "/post";
+static const char kServerResponse[] = "This is a test";
+
+class TestHttpServer : public HttpServer, public sigslot::has_slots<> {
+ public:
+ TestHttpServer(Thread* thread, const SocketAddress& addr)
+ : socket_(thread->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+ socket_->Bind(addr);
+ socket_->Listen(5);
+ socket_->SignalReadEvent.connect(this, &TestHttpServer::OnAccept);
+ }
+
+ SocketAddress address() const { return socket_->GetLocalAddress(); }
+
+ private:
+ void OnAccept(AsyncSocket* socket) {
+ AsyncSocket* new_socket = socket_->Accept(NULL);
+ if (new_socket) {
+ HandleConnection(new SocketStream(new_socket));
+ }
+ }
+ talk_base::scoped_ptr<AsyncSocket> socket_;
+};
+
+class AsyncHttpRequestTest : public testing::Test,
+ public sigslot::has_slots<> {
+ public:
+ AsyncHttpRequestTest()
+ : done_(false), server_(Thread::Current(), kServerAddr) {
+ server_.SignalHttpRequest.connect(this, &AsyncHttpRequestTest::OnRequest);
+ }
+
+ bool done() const { return done_; }
+
+ AsyncHttpRequest* CreateGetRequest(const std::string& host, int port,
+ const std::string& path) {
+ talk_base::AsyncHttpRequest* request =
+ new talk_base::AsyncHttpRequest("unittest");
+ request->SignalWorkDone.connect(this,
+ &AsyncHttpRequestTest::OnRequestDone);
+ request->request().verb = talk_base::HV_GET;
+ request->set_host(host);
+ request->set_port(port);
+ request->request().path = path;
+ request->response().document.reset(new MemoryStream());
+ return request;
+ }
+ AsyncHttpRequest* CreatePostRequest(const std::string& host, int port,
+ const std::string& path,
+ const std::string content_type,
+ StreamInterface* content) {
+ talk_base::AsyncHttpRequest* request =
+ new talk_base::AsyncHttpRequest("unittest");
+ request->SignalWorkDone.connect(this,
+ &AsyncHttpRequestTest::OnRequestDone);
+ request->request().verb = talk_base::HV_POST;
+ request->set_host(host);
+ request->set_port(port);
+ request->request().path = path;
+ request->request().setContent(content_type, content);
+ request->response().document.reset(new MemoryStream());
+ return request;
+ }
+
+ const TestHttpServer& server() const { return server_; }
+
+ protected:
+ void OnRequest(HttpServer* server, HttpServerTransaction* t) {
+ if (t->request.path == kServerGetPath) {
+ t->response.set_success("text/plain", new MemoryStream(kServerResponse));
+ } else if (t->request.path == kServerPostPath) {
+ // reverse the data and reply
+ size_t size;
+ StreamInterface* in = t->request.document.get();
+ StreamInterface* out = new MemoryStream();
+ in->GetSize(&size);
+ for (size_t i = 0; i < size; ++i) {
+ char ch;
+ in->SetPosition(size - i - 1);
+ in->Read(&ch, 1, NULL, NULL);
+ out->Write(&ch, 1, NULL, NULL);
+ }
+ out->Rewind();
+ t->response.set_success("text/plain", out);
+ } else {
+ t->response.set_error(404);
+ }
+ server_.Respond(t);
+ }
+ void OnRequestDone(SignalThread* thread) {
+ done_ = true;
+ }
+
+ private:
+ bool done_;
+ TestHttpServer server_;
+};
+
+TEST_F(AsyncHttpRequestTest, TestGetSuccess) {
+ AsyncHttpRequest* req = CreateGetRequest(
+ kServerHostnameAddr.hostname(), server().address().port(),
+ kServerGetPath);
+ req->Start();
+ EXPECT_TRUE_WAIT(done(), 5000);
+ std::string response;
+ EXPECT_EQ(200U, req->response().scode);
+ ASSERT_TRUE(req->response().document.get() != NULL);
+ req->response().document->Rewind();
+ req->response().document->ReadLine(&response);
+ EXPECT_EQ(kServerResponse, response);
+ req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestGetNotFound) {
+ AsyncHttpRequest* req = CreateGetRequest(
+ kServerHostnameAddr.hostname(), server().address().port(),
+ "/bad");
+ req->Start();
+ EXPECT_TRUE_WAIT(done(), 5000);
+ size_t size;
+ EXPECT_EQ(404U, req->response().scode);
+ ASSERT_TRUE(req->response().document.get() != NULL);
+ req->response().document->GetSize(&size);
+ EXPECT_EQ(0U, size);
+ req->Release();
+}
+
+TEST_F(AsyncHttpRequestTest, TestPostSuccess) {
+ AsyncHttpRequest* req = CreatePostRequest(
+ kServerHostnameAddr.hostname(), server().address().port(),
+ kServerPostPath, "text/plain", new MemoryStream("abcd1234"));
+ req->Start();
+ EXPECT_TRUE_WAIT(done(), 5000);
+ std::string response;
+ EXPECT_EQ(200U, req->response().scode);
+ ASSERT_TRUE(req->response().document.get() != NULL);
+ req->response().document->Rewind();
+ req->response().document->ReadLine(&response);
+ EXPECT_EQ("4321dcba", response);
+ req->Release();
+}
+
+// Ensure that we shut down properly even if work is outstanding.
+TEST_F(AsyncHttpRequestTest, TestCancel) {
+ AsyncHttpRequest* req = CreateGetRequest(
+ kServerHostnameAddr.hostname(), server().address().port(),
+ kServerGetPath);
+ req->Start();
+ req->Destroy(true);
+}
+
+} // namespace talk_base
diff --git a/talk/base/autodetectproxy_unittest.cc b/talk/base/autodetectproxy_unittest.cc
new file mode 100644
index 0000000..bdf2786
--- /dev/null
+++ b/talk/base/autodetectproxy_unittest.cc
@@ -0,0 +1,100 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/autodetectproxy.h"
+#include "talk/base/gunit.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+
+namespace talk_base {
+
+static const char kUserAgent[] = "";
+static const char kPath[] = "/";
+static const char kHost[] = "relay.google.com";
+static const uint16 kPort = 443;
+static const bool kSecure = true;
+// Each of the two stages in AutoDetectProxy has a 2-second time-out, so 5
+// seconds total should be enough.
+static const int kTimeoutMs = 5000;
+
+class AutoDetectProxyTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ AutoDetectProxyTest() : auto_detect_proxy_(NULL), done_(false) {}
+
+ protected:
+ bool Create(const std::string &user_agent,
+ const std::string &path,
+ const std::string &host,
+ uint16 port,
+ bool secure) {
+ auto_detect_proxy_ = new AutoDetectProxy(user_agent);
+ EXPECT_TRUE(auto_detect_proxy_ != NULL);
+ if (!auto_detect_proxy_) {
+ return false;
+ }
+ Url<char> host_url(path, host, port);
+ host_url.set_secure(secure);
+ auto_detect_proxy_->set_server_url(host_url.url());
+ auto_detect_proxy_->SignalWorkDone.connect(
+ this,
+ &AutoDetectProxyTest::OnWorkDone);
+ auto_detect_proxy_->Start();
+ return true;
+ }
+
+ bool Run(int timeout_ms) {
+ EXPECT_TRUE_WAIT(done_, timeout_ms);
+ return done_;
+ }
+
+ private:
+ void OnWorkDone(talk_base::SignalThread *thread) {
+ AutoDetectProxy *auto_detect_proxy =
+ static_cast<talk_base::AutoDetectProxy *>(thread);
+ EXPECT_TRUE(auto_detect_proxy == auto_detect_proxy_);
+ auto_detect_proxy_ = NULL;
+ auto_detect_proxy->Release();
+ done_ = true;
+ }
+
+ AutoDetectProxy *auto_detect_proxy_;
+ bool done_;
+};
+
+// Test that proxy detection completes successfully. (Does not actually verify
+// the correct detection result since we don't know what proxy to expect on an
+// arbitrary machine.)
+TEST_F(AutoDetectProxyTest, TestProxyDetection) {
+ ASSERT_TRUE(Create(kUserAgent,
+ kPath,
+ kHost,
+ kPort,
+ kSecure));
+ ASSERT_TRUE(Run(kTimeoutMs));
+}
+
+} // namespace talk_base
diff --git a/talk/base/bytebuffer_unittest.cc b/talk/base/bytebuffer_unittest.cc
new file mode 100644
index 0000000..2970fdc
--- /dev/null
+++ b/talk/base/bytebuffer_unittest.cc
@@ -0,0 +1,185 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(ByteBufferTest, TestByteOrder) {
+ uint16 n16 = 1;
+ uint32 n32 = 1;
+ uint64 n64 = 1;
+
+ EXPECT_EQ(n16, NetworkToHost16(HostToNetwork16(n16)));
+ EXPECT_EQ(n32, NetworkToHost32(HostToNetwork32(n32)));
+ EXPECT_EQ(n64, NetworkToHost64(HostToNetwork64(n64)));
+
+ if (IsHostBigEndian()) {
+ // The host is the network (big) endian.
+ EXPECT_EQ(n16, HostToNetwork16(n16));
+ EXPECT_EQ(n32, HostToNetwork32(n32));
+ EXPECT_EQ(n64, HostToNetwork64(n64));
+
+ // GetBE converts big endian to little endian here.
+ EXPECT_EQ(n16 >> 8, GetBE16(&n16));
+ EXPECT_EQ(n32 >> 24, GetBE32(&n32));
+ EXPECT_EQ(n64 >> 56, GetBE64(&n64));
+ } else {
+ // The host is little endian.
+ EXPECT_NE(n16, HostToNetwork16(n16));
+ EXPECT_NE(n32, HostToNetwork32(n32));
+ EXPECT_NE(n64, HostToNetwork64(n64));
+
+ // GetBE converts little endian to big endian here.
+ EXPECT_EQ(GetBE16(&n16), HostToNetwork16(n16));
+ EXPECT_EQ(GetBE32(&n32), HostToNetwork32(n32));
+ EXPECT_EQ(GetBE64(&n64), HostToNetwork64(n64));
+
+ // GetBE converts little endian to big endian here.
+ EXPECT_EQ(n16 << 8, GetBE16(&n16));
+ EXPECT_EQ(n32 << 24, GetBE32(&n32));
+ EXPECT_EQ(n64 << 56, GetBE64(&n64));
+ }
+}
+
+TEST(ByteBufferTest, TestBufferLength) {
+ ByteBuffer buffer;
+ size_t size = 0;
+ EXPECT_EQ(size, buffer.Length());
+
+ buffer.WriteUInt8(1);
+ ++size;
+ EXPECT_EQ(size, buffer.Length());
+
+ buffer.WriteUInt16(1);
+ size += 2;
+ EXPECT_EQ(size, buffer.Length());
+
+ buffer.WriteUInt24(1);
+ size += 3;
+ EXPECT_EQ(size, buffer.Length());
+
+ buffer.WriteUInt32(1);
+ size += 4;
+ EXPECT_EQ(size, buffer.Length());
+
+ buffer.WriteUInt64(1);
+ size += 8;
+ EXPECT_EQ(size, buffer.Length());
+
+ EXPECT_TRUE(buffer.Consume(0));
+ EXPECT_EQ(size, buffer.Length());
+
+ EXPECT_TRUE(buffer.Consume(4));
+ size -= 4;
+ EXPECT_EQ(size, buffer.Length());
+
+ EXPECT_TRUE(buffer.Shift(4));
+ size -= 4;
+ EXPECT_EQ(size, buffer.Length());
+}
+
+TEST(ByteBufferTest, TestReadWriteBuffer) {
+ ByteBuffer::ByteOrder orders[2] = { ByteBuffer::ORDER_HOST,
+ ByteBuffer::ORDER_NETWORK };
+ for (size_t i = 0; i < ARRAY_SIZE(orders); i++) {
+ ByteBuffer buffer(orders[i]);
+ uint8 ru8;
+ EXPECT_FALSE(buffer.ReadUInt8(&ru8));
+
+ // Write and read uint8.
+ uint8 wu8 = 1;
+ buffer.WriteUInt8(wu8);
+ EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+ EXPECT_EQ(wu8, ru8);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read uint16.
+ uint16 wu16 = (1 << 8) + 1;
+ buffer.WriteUInt16(wu16);
+ uint16 ru16;
+ EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+ EXPECT_EQ(wu16, ru16);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read uint24.
+ uint32 wu24 = (3 << 16) + (2 << 8) + 1;
+ buffer.WriteUInt24(wu24);
+ uint32 ru24;
+ EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+ EXPECT_EQ(wu24, ru24);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read uint32.
+ uint32 wu32 = (4 << 24) + (3 << 16) + (2 << 8) + 1;
+ buffer.WriteUInt32(wu32);
+ uint32 ru32;
+ EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+ EXPECT_EQ(wu32, ru32);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read uint64.
+ uint32 another32 = (8 << 24) + (7 << 16) + (6 << 8) + 5;
+ uint64 wu64 = (static_cast<uint64>(another32) << 32) + wu32;
+ buffer.WriteUInt64(wu64);
+ uint64 ru64;
+ EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+ EXPECT_EQ(wu64, ru64);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read string.
+ std::string write_string("hello");
+ buffer.WriteString(write_string);
+ std::string read_string;
+ EXPECT_TRUE(buffer.ReadString(&read_string, write_string.size()));
+ EXPECT_EQ(write_string, read_string);
+ EXPECT_EQ(0U, buffer.Length());
+
+ // Write and read in order.
+ buffer.WriteUInt8(wu8);
+ buffer.WriteUInt16(wu16);
+ buffer.WriteUInt24(wu24);
+ buffer.WriteUInt32(wu32);
+ buffer.WriteUInt64(wu64);
+ EXPECT_TRUE(buffer.ReadUInt8(&ru8));
+ EXPECT_EQ(wu8, ru8);
+ EXPECT_TRUE(buffer.ReadUInt16(&ru16));
+ EXPECT_EQ(wu16, ru16);
+ EXPECT_TRUE(buffer.ReadUInt24(&ru24));
+ EXPECT_EQ(wu24, ru24);
+ EXPECT_TRUE(buffer.ReadUInt32(&ru32));
+ EXPECT_EQ(wu32, ru32);
+ EXPECT_TRUE(buffer.ReadUInt64(&ru64));
+ EXPECT_EQ(wu64, ru64);
+ EXPECT_EQ(0U, buffer.Length());
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/event_unittest.cc b/talk/base/event_unittest.cc
new file mode 100644
index 0000000..5a3c1c6
--- /dev/null
+++ b/talk/base/event_unittest.cc
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/event.h"
+#include "talk/base/gunit.h"
+
+namespace talk_base {
+
+TEST(EventTest, InitiallySignaled) {
+ Event event(false, true);
+ ASSERT_TRUE(event.Wait(0));
+}
+
+TEST(EventTest, ManualReset) {
+ Event event(true, false);
+ ASSERT_FALSE(event.Wait(0));
+
+ event.Set();
+ ASSERT_TRUE(event.Wait(0));
+ ASSERT_TRUE(event.Wait(0));
+
+ event.Reset();
+ ASSERT_FALSE(event.Wait(0));
+}
+
+TEST(EventTest, AutoReset) {
+ Event event(false, false);
+ ASSERT_FALSE(event.Wait(0));
+
+ event.Set();
+ ASSERT_TRUE(event.Wait(0));
+ ASSERT_FALSE(event.Wait(0));
+}
+
+} // namespace talk_base
diff --git a/talk/base/fileutils_mock.h b/talk/base/fileutils_mock.h
new file mode 100644
index 0000000..2ccb6a3
--- /dev/null
+++ b/talk/base/fileutils_mock.h
@@ -0,0 +1,270 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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.
+ */
+
+#ifndef TALK_BASE_FILEUTILS_MOCK_H_
+#define TALK_BASE_FILEUTILS_MOCK_H_
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "testing/base/public/gmock.h"
+
+namespace talk_base {
+
+class FakeFileStream : public FileStream {
+ public:
+ explicit FakeFileStream(const std::string & contents) :
+ string_stream_(contents)
+ {}
+
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ return string_stream_.Read(buffer, buffer_len, read, error);
+ }
+
+ virtual void Close() {
+ return string_stream_.Close();
+ }
+ virtual bool GetSize(size_t* size) const {
+ return string_stream_.GetSize(size);
+ }
+
+ private:
+ StringStream string_stream_;
+};
+
+class FakeDirectoryIterator : public DirectoryIterator {
+ public:
+ typedef std::pair<std::string, std::string> File;
+
+ /*
+ * files should be sorted by directory
+ * put '/' at the end of file if you want it to be a directory
+ *
+ * Sample list:
+ * /var/dir/file1
+ * /var/dir/file2
+ * /var/dir/subdir1/
+ * /var/dir/subdir2/
+ * /var/dir2/file2
+ * /var/dir3/
+ *
+ * you can call Iterate for any path: /var, /var/dir, /var/dir2
+ * unrelated files will be ignored
+ */
+ explicit FakeDirectoryIterator(const std::vector<File>& all_files) :
+ all_files_(all_files) {}
+
+ virtual bool Iterate(const Pathname& path) {
+ path_iterator_ = all_files_.begin();
+ path_ = path.pathname();
+
+ // make sure path ends end with '/'
+ if (path_.rfind(Pathname::DefaultFolderDelimiter()) != path_.size() - 1)
+ path_ += Pathname::DefaultFolderDelimiter();
+
+ return FakeDirectoryIterator::Search(std::string(""));
+ }
+
+ virtual bool Next() {
+ std::string current_name = Name();
+ path_iterator_++;
+ return FakeDirectoryIterator::Search(current_name);
+ }
+
+ bool Search(const std::string& current_name) {
+ for (; path_iterator_ != all_files_.end(); path_iterator_++) {
+ if (path_iterator_->first.find(path_) == 0
+ && Name().compare(current_name) != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ virtual bool IsDirectory() const {
+ std::string sub_path = path_iterator_->first;
+
+ return std::string::npos !=
+ sub_path.find(Pathname::DefaultFolderDelimiter(), path_.size());
+ }
+
+ virtual std::string Name() const {
+ std::string sub_path = path_iterator_->first;
+
+ // path - top level path (ex. /var/lib)
+ // sub_path - subpath under top level path (ex. /var/lib/dir/dir/file )
+ // find shortest non-trivial common path. (ex. /var/lib/dir)
+ size_t start = path_.size();
+ size_t end = sub_path.find(Pathname::DefaultFolderDelimiter(), start);
+
+ if (end != std::string::npos) {
+ return sub_path.substr(start, end - start);
+ } else {
+ return sub_path.substr(start);
+ }
+ }
+
+ private:
+ const std::vector<File> all_files_;
+
+ std::string path_;
+ std::vector<File>::const_iterator path_iterator_;
+};
+
+class FakeFileSystem : public FilesystemInterface {
+ public:
+ typedef std::pair<std::string, std::string> File;
+
+ explicit FakeFileSystem(const std::vector<File>& all_files) :
+ all_files_(all_files) {}
+
+ virtual DirectoryIterator *IterateDirectory() {
+ return new FakeDirectoryIterator(all_files_);
+ }
+
+ virtual FileStream * OpenFile(
+ const Pathname &filename,
+ const std::string &mode) {
+ std::vector<File>::const_iterator i_files = all_files_.begin();
+ std::string path = filename.pathname();
+
+ for (; i_files != all_files_.end(); i_files++) {
+ if (i_files->first.compare(path) == 0) {
+ return new FakeFileStream(i_files->second);
+ }
+ }
+
+ return NULL;
+ }
+
+ bool CreatePrivateFile(const Pathname &filename) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool DeleteFile(const Pathname &filename) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool DeleteEmptyFolder(const Pathname &folder) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool DeleteFolderContents(const Pathname &folder) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool DeleteFolderAndContents(const Pathname &folder) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool CreateFolder(const Pathname &pathname) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool IsFolder(const Pathname &pathname) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool IsFile(const Pathname &pathname) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool IsAbsent(const Pathname &pathname) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool IsTemporaryPath(const Pathname &pathname) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool GetTemporaryFolder(Pathname &path, bool create,
+ const std::string *append) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ std::string TempFilename(const Pathname &dir, const std::string &prefix) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return std::string();
+ }
+ bool GetFileSize(const Pathname &path, size_t *size) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool GetFileTime(const Pathname &path, FileTimeType which,
+ time_t* time) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool GetAppPathname(Pathname *path) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool GetAppDataFolder(Pathname *path, bool per_user) {
+ EXPECT_TRUE_M(per_user, "Unsupported operation");
+#ifdef WIN32
+ path->SetPathname("c:\\Users\\test_user", "");
+#else
+ path->SetPathname("/home/user/test_user", "");
+#endif
+ return true;
+ }
+ bool GetAppTempFolder(Pathname *path) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ bool GetDiskFreeSpace(const Pathname &path, int64 *freebytes) {
+ EXPECT_TRUE_M(false, "Unsupported operation");
+ return false;
+ }
+ Pathname GetCurrentDirectory() {
+ return Pathname();
+ }
+
+ private:
+ const std::vector<File> all_files_;
+};
+} // namespace talk_base
+
+#endif // TALK_BASE_FILEUTILS_MOCK_H_
diff --git a/talk/base/fileutils_unittest.cc b/talk/base/fileutils_unittest.cc
new file mode 100644
index 0000000..992ea82
--- /dev/null
+++ b/talk/base/fileutils_unittest.cc
@@ -0,0 +1,148 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+// Make sure we can get a temp folder for the later tests.
+TEST(FilesystemTest, GetTemporaryFolder) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+}
+
+// Test creating a temp file, reading it back in, and deleting it.
+TEST(FilesystemTest, TestOpenFile) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+ FileStream* fs;
+ char buf[256];
+ size_t bytes;
+
+ fs = Filesystem::OpenFile(path, "wb");
+ ASSERT_TRUE(fs != NULL);
+ EXPECT_EQ(SR_SUCCESS, fs->Write("test", 4, &bytes, NULL));
+ EXPECT_EQ(4U, bytes);
+ delete fs;
+
+ EXPECT_TRUE(Filesystem::IsFile(path));
+
+ fs = Filesystem::OpenFile(path, "rb");
+ ASSERT_TRUE(fs != NULL);
+ EXPECT_EQ(SR_SUCCESS, fs->Read(buf, sizeof(buf), &bytes, NULL));
+ EXPECT_EQ(4U, bytes);
+ delete fs;
+
+ EXPECT_TRUE(Filesystem::DeleteFile(path));
+ EXPECT_FALSE(Filesystem::IsFile(path));
+}
+
+// Test opening a non-existent file.
+TEST(FilesystemTest, TestOpenBadFile) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetFilename("not an actual file");
+
+ EXPECT_FALSE(Filesystem::IsFile(path));
+
+ FileStream* fs = Filesystem::OpenFile(path, "rb");
+ EXPECT_FALSE(fs != NULL);
+}
+
+// Test that CreatePrivateFile fails for existing files and succeeds for
+// non-existent ones.
+TEST(FilesystemTest, TestCreatePrivateFile) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetFilename("private_file_test");
+
+ // First call should succeed because the file doesn't exist yet.
+ EXPECT_TRUE(Filesystem::CreatePrivateFile(path));
+ // Next call should fail, because now it exists.
+ EXPECT_FALSE(Filesystem::CreatePrivateFile(path));
+
+ // Verify that we have permission to open the file for reading and writing.
+ scoped_ptr<FileStream> fs(Filesystem::OpenFile(path, "wb"));
+ EXPECT_TRUE(fs.get() != NULL);
+ // Have to close the file on Windows before it will let us delete it.
+ fs.reset();
+
+ // Verify that we have permission to delete the file.
+ EXPECT_TRUE(Filesystem::DeleteFile(path));
+}
+
+// Test checking for free disk space.
+TEST(FilesystemTest, TestGetDiskFreeSpace) {
+ // Note that we should avoid picking any file/folder which could be located
+ // at the remotely mounted drive/device.
+ Pathname path;
+ ASSERT_TRUE(Filesystem::GetAppDataFolder(&path, true));
+
+ int64 free1 = 0;
+ EXPECT_TRUE(Filesystem::IsFolder(path));
+ EXPECT_FALSE(Filesystem::IsFile(path));
+ EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free1));
+ EXPECT_GT(free1, 0);
+
+ int64 free2 = 0;
+ path.AppendFolder("this_folder_doesnt_exist");
+ EXPECT_FALSE(Filesystem::IsFolder(path));
+ EXPECT_TRUE(Filesystem::IsAbsent(path));
+ EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free2));
+ // These should be the same disk, and disk free space should not have changed
+ // by more than 1% between the two calls.
+ EXPECT_LT(static_cast<int64>(free1 * .9), free2);
+ EXPECT_LT(free2, static_cast<int64>(free1 * 1.1));
+
+ int64 free3 = 0;
+ path.clear();
+ EXPECT_TRUE(path.empty());
+ EXPECT_TRUE(Filesystem::GetDiskFreeSpace(path, &free3));
+ // Current working directory may not be where exe is.
+ // EXPECT_LT(static_cast<int64>(free1 * .9), free3);
+ // EXPECT_LT(free3, static_cast<int64>(free1 * 1.1));
+ EXPECT_GT(free3, 0);
+}
+
+// Tests that GetCurrentDirectory() returns something.
+TEST(FilesystemTest, TestGetCurrentDirectory) {
+ EXPECT_FALSE(Filesystem::GetCurrentDirectory().empty());
+}
+
+// Tests that GetAppPathname returns something.
+TEST(FilesystemTest, TestGetAppPathname) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetAppPathname(&path));
+ EXPECT_FALSE(path.empty());
+}
+
+} // namespace talk_base
diff --git a/talk/base/helpers_unittest.cc b/talk/base/helpers_unittest.cc
new file mode 100644
index 0000000..0fe1d5b
--- /dev/null
+++ b/talk/base/helpers_unittest.cc
@@ -0,0 +1,83 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 <string>
+
+#include "talk/base/gunit.h"
+#include "talk/base/helpers.h"
+
+namespace talk_base {
+
+TEST(RandomTest, TestCreateRandomId) {
+ CreateRandomId();
+}
+
+TEST(RandomTest, TestCreateRandomDouble) {
+ for (int i = 0; i < 100; ++i) {
+ double r = CreateRandomDouble();
+ EXPECT_GE(r, 0.0);
+ EXPECT_LT(r, 1.0);
+ }
+}
+
+TEST(RandomTest, TestCreateNonZeroRandomId) {
+ EXPECT_NE(0U, CreateRandomNonZeroId());
+}
+
+TEST(RandomTest, TestCreateRandomString) {
+ std::string random = CreateRandomString(256);
+ EXPECT_EQ(256U, random.size());
+ std::string random2;
+ EXPECT_TRUE(CreateRandomString(256, &random2));
+ EXPECT_NE(random, random2);
+ EXPECT_EQ(256U, random2.size());
+}
+
+TEST(RandomTest, TestCreateRandomForTest) {
+ // Make sure we get the output we expect.
+ SetRandomTestMode(true);
+ EXPECT_EQ(2154761789U, CreateRandomId());
+ EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+ // Reset and make sure we get the same output.
+ SetRandomTestMode(true);
+ EXPECT_EQ(2154761789U, CreateRandomId());
+ EXPECT_EQ("h0ISP4S5SJKH/9EY", CreateRandomString(16));
+
+ // Test different character sets.
+ SetRandomTestMode(true);
+ std::string str;
+ EXPECT_TRUE(CreateRandomString(16, "a", &str));
+ EXPECT_EQ("aaaaaaaaaaaaaaaa", str);
+ EXPECT_TRUE(CreateRandomString(16, "abc", &str));
+ EXPECT_EQ("acbccaaaabbaacbb", str);
+
+ // Turn off test mode for other tests.
+ SetRandomTestMode(false);
+}
+
+} // namespace talk_base
diff --git a/talk/base/host_unittest.cc b/talk/base/host_unittest.cc
new file mode 100644
index 0000000..aba87af
--- /dev/null
+++ b/talk/base/host_unittest.cc
@@ -0,0 +1,33 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/host.h"
+
+TEST(Host, GetHostName) {
+ EXPECT_NE("", talk_base::GetHostName());
+}
diff --git a/talk/base/httpbase_unittest.cc b/talk/base/httpbase_unittest.cc
new file mode 100644
index 0000000..73ef949
--- /dev/null
+++ b/talk/base/httpbase_unittest.cc
@@ -0,0 +1,536 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/testutils.h"
+
+namespace talk_base {
+
+const char* const kHttpResponse =
+ "HTTP/1.1 200\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "Proxy-Authorization: 42\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "00000008\r\n"
+ "Goodbye!\r\n"
+ "0\r\n\r\n";
+
+const char* const kHttpEmptyResponse =
+ "HTTP/1.1 200\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Length: 0\r\n"
+ "Proxy-Authorization: 42\r\n"
+ "\r\n";
+
+const char* const kHttpResponsePrefix =
+ "HTTP/1.1 200\r\n"
+ "Connection: Keep-Alive\r\n"
+ "Content-Type: text/plain\r\n"
+ "Proxy-Authorization: 42\r\n"
+ "Transfer-Encoding: chunked\r\n"
+ "\r\n"
+ "8\r\n"
+ "Goodbye!\r\n";
+
+class HttpBaseTest : public testing::Test, public IHttpNotify {
+public:
+ enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
+ struct Event {
+ EventType event;
+ bool chunked;
+ size_t data_size;
+ HttpMode mode;
+ HttpError err;
+ };
+ HttpBaseTest() : mem(NULL), obtain_stream(false), http_stream(NULL) { }
+
+ virtual void SetUp() { }
+ virtual void TearDown() {
+ // Avoid an ASSERT, in case a test doesn't clean up properly
+ base.abort(HE_NONE);
+ }
+
+ virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
+ LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
+ Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
+ events.push_back(e);
+ if (obtain_stream) {
+ ObtainDocumentStream();
+ }
+ return HE_NONE;
+ }
+ virtual void onHttpComplete(HttpMode mode, HttpError err) {
+ LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
+ Event e = { E_COMPLETE, false, 0, mode, err };
+ events.push_back(e);
+ }
+ virtual void onHttpClosed(HttpError err) {
+ LOG_F(LS_VERBOSE) << "err: " << err;
+ Event e = { E_CLOSED, false, 0, HM_NONE, err };
+ events.push_back(e);
+ }
+
+ void SetupSource(const char* response);
+
+ void VerifyHeaderComplete(size_t event_count, bool empty_doc);
+ void VerifyDocumentContents(const char* expected_data,
+ size_t expected_length = SIZE_UNKNOWN);
+
+ void ObtainDocumentStream();
+ void VerifyDocumentStreamIsOpening();
+ void VerifyDocumentStreamOpenEvent();
+ void ReadDocumentStreamData(const char* expected_data);
+ void VerifyDocumentStreamIsEOS();
+
+ void SetupDocument(const char* response);
+ void VerifySourceContents(const char* expected_data,
+ size_t expected_length = SIZE_UNKNOWN);
+
+ void VerifyTransferComplete(HttpMode mode, HttpError error);
+
+ HttpBase base;
+ MemoryStream* mem;
+ HttpResponseData data;
+
+ // The source of http data, and source events
+ testing::StreamSource src;
+ std::vector<Event> events;
+
+ // Document stream, and stream events
+ bool obtain_stream;
+ StreamInterface* http_stream;
+ testing::StreamSink sink;
+};
+
+void HttpBaseTest::SetupSource(const char* http_data) {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ src.SetState(SS_OPENING);
+ src.QueueString(http_data);
+
+ base.notify(this);
+ base.attach(&src);
+ EXPECT_TRUE(events.empty());
+
+ src.SetState(SS_OPEN);
+ ASSERT_EQ(1U, events.size());
+ EXPECT_EQ(E_COMPLETE, events[0].event);
+ EXPECT_EQ(HM_CONNECT, events[0].mode);
+ EXPECT_EQ(HE_NONE, events[0].err);
+ events.clear();
+
+ mem = new MemoryStream;
+ data.document.reset(mem);
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ ASSERT_EQ(event_count, events.size());
+ EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
+
+ std::string header;
+ EXPECT_EQ(HVER_1_1, data.version);
+ EXPECT_EQ(static_cast<uint32>(HC_OK), data.scode);
+ EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
+ EXPECT_EQ("42", header);
+ EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
+ EXPECT_EQ("Keep-Alive", header);
+
+ if (empty_doc) {
+ EXPECT_FALSE(events[0].chunked);
+ EXPECT_EQ(0U, events[0].data_size);
+
+ EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
+ EXPECT_EQ("0", header);
+ } else {
+ EXPECT_TRUE(events[0].chunked);
+ EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
+
+ EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
+ EXPECT_EQ("text/plain", header);
+ EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
+ EXPECT_EQ("chunked", header);
+ }
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
+ size_t expected_length) {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ if (SIZE_UNKNOWN == expected_length) {
+ expected_length = strlen(expected_data);
+ }
+ EXPECT_EQ(mem, data.document.get());
+
+ size_t length;
+ mem->GetSize(&length);
+ EXPECT_EQ(expected_length, length);
+ EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ObtainDocumentStream() {
+ LOG_F(LS_VERBOSE) << "Enter";
+ EXPECT_FALSE(http_stream);
+ http_stream = base.GetDocumentStream();
+ ASSERT_TRUE(NULL != http_stream);
+ sink.Monitor(http_stream);
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsOpening() {
+ LOG_F(LS_VERBOSE) << "Enter";
+ ASSERT_TRUE(NULL != http_stream);
+ EXPECT_EQ(0, sink.Events(http_stream));
+ EXPECT_EQ(SS_OPENING, http_stream->GetState());
+
+ size_t read = 0;
+ char buffer[5] = { 0 };
+ EXPECT_EQ(SR_BLOCK, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ ASSERT_TRUE(NULL != http_stream);
+ EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
+ EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+ // HTTP headers haven't arrived yet
+ EXPECT_EQ(0U, events.size());
+ EXPECT_EQ(static_cast<uint32>(HC_INTERNAL_SERVER_ERROR), data.scode);
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ ASSERT_TRUE(NULL != http_stream);
+ EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+ // Pump the HTTP I/O using Read, and verify the results.
+ size_t verified_length = 0;
+ const size_t expected_length = strlen(expected_data);
+ while (verified_length < expected_length) {
+ size_t read = 0;
+ char buffer[5] = { 0 };
+ size_t amt_to_read = _min(expected_length - verified_length, sizeof(buffer));
+ EXPECT_EQ(SR_SUCCESS, http_stream->Read(buffer, amt_to_read, &read, NULL));
+ EXPECT_EQ(amt_to_read, read);
+ EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
+ verified_length += read;
+ }
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyDocumentStreamIsEOS() {
+ LOG_F(LS_VERBOSE) << "Enter";
+
+ ASSERT_TRUE(NULL != http_stream);
+ size_t read = 0;
+ char buffer[5] = { 0 };
+ EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, NULL));
+ EXPECT_EQ(SS_CLOSED, http_stream->GetState());
+
+ // When EOS is caused by Read, we don't expect SE_CLOSE
+ EXPECT_EQ(0, sink.Events(http_stream));
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::SetupDocument(const char* document_data) {
+ LOG_F(LS_VERBOSE) << "Enter";
+ src.SetState(SS_OPEN);
+
+ base.notify(this);
+ base.attach(&src);
+ EXPECT_TRUE(events.empty());
+
+ if (document_data) {
+ // Note: we could just call data.set_success("text/plain", mem), but that
+ // won't allow us to use the chunked transfer encoding.
+ mem = new MemoryStream(document_data);
+ data.document.reset(mem);
+ data.setHeader(HH_CONTENT_TYPE, "text/plain");
+ data.setHeader(HH_TRANSFER_ENCODING, "chunked");
+ } else {
+ data.setHeader(HH_CONTENT_LENGTH, "0");
+ }
+ data.scode = HC_OK;
+ data.setHeader(HH_PROXY_AUTHORIZATION, "42");
+ data.setHeader(HH_CONNECTION, "Keep-Alive");
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifySourceContents(const char* expected_data,
+ size_t expected_length) {
+ LOG_F(LS_VERBOSE) << "Enter";
+ if (SIZE_UNKNOWN == expected_length) {
+ expected_length = strlen(expected_data);
+ }
+ std::string contents = src.ReadData();
+ EXPECT_EQ(expected_length, contents.length());
+ EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
+ LOG_F(LS_VERBOSE) << "Enter";
+ // Verify that http operation has completed
+ ASSERT_TRUE(events.size() > 0);
+ size_t last_event = events.size() - 1;
+ EXPECT_EQ(E_COMPLETE, events[last_event].event);
+ EXPECT_EQ(mode, events[last_event].mode);
+ EXPECT_EQ(error, events[last_event].err);
+ LOG_F(LS_VERBOSE) << "Exit";
+}
+
+//
+// Tests
+//
+
+TEST_F(HttpBaseTest, SupportsSend) {
+ // Queue response document
+ SetupDocument("Goodbye!");
+
+ // Begin send
+ base.send(&data);
+
+ // Send completed successfully
+ VerifyTransferComplete(HM_SEND, HE_NONE);
+ VerifySourceContents(kHttpResponse);
+}
+
+TEST_F(HttpBaseTest, SupportsSendNoDocument) {
+ // Queue response document
+ SetupDocument(NULL);
+
+ // Begin send
+ base.send(&data);
+
+ // Send completed successfully
+ VerifyTransferComplete(HM_SEND, HE_NONE);
+ VerifySourceContents(kHttpEmptyResponse);
+}
+
+TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
+ // This test is attempting to expose a bug that occurs when a particular
+ // base objects is used for receiving, and then used for sending. In
+ // particular, the HttpParser state is different after receiving. Simulate
+ // that here.
+ SetupSource(kHttpResponse);
+ base.recv(&data);
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+
+ src.Clear();
+ data.clear(true);
+ events.clear();
+ base.detach();
+
+ // Queue response document
+ SetupDocument("Goodbye!");
+
+ // Prevent entire response from being sent
+ const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
+ src.SetWriteBlock(kInterruptedLength);
+
+ // Begin send
+ base.send(&data);
+
+ // Document is mostly complete, but no completion signal yet.
+ EXPECT_TRUE(events.empty());
+ VerifySourceContents(kHttpResponse, kInterruptedLength);
+
+ src.SetState(SS_CLOSED);
+
+ // Send completed with disconnect error, and no additional data.
+ VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
+ EXPECT_TRUE(src.ReadData().empty());
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
+ // Queue response document
+ SetupSource(kHttpResponse);
+
+ // Begin receive
+ base.recv(&data);
+
+ // Document completed successfully
+ VerifyHeaderComplete(2, false);
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+ VerifyDocumentContents("Goodbye!");
+}
+
+TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
+ // Switch to pull mode
+ ObtainDocumentStream();
+ VerifyDocumentStreamIsOpening();
+
+ // Queue response document
+ SetupSource(kHttpResponse);
+ VerifyDocumentStreamIsOpening();
+
+ // Begin receive
+ base.recv(&data);
+
+ // Pull document data
+ VerifyDocumentStreamOpenEvent();
+ ReadDocumentStreamData("Goodbye!");
+ VerifyDocumentStreamIsEOS();
+
+ // Document completed successfully
+ VerifyHeaderComplete(2, false);
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+ VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
+
+ // TODO: Remove extra logging once test failure is understood
+ int old_sev = talk_base::LogMessage::GetLogToDebug();
+ talk_base::LogMessage::LogToDebug(LS_VERBOSE);
+
+
+ // Switch to pull mode
+ ObtainDocumentStream();
+ VerifyDocumentStreamIsOpening();
+
+ // Queue response document
+ SetupSource(kHttpResponse);
+ VerifyDocumentStreamIsOpening();
+
+ // Begin receive
+ base.recv(&data);
+
+ // Pull some of the data
+ VerifyDocumentStreamOpenEvent();
+ ReadDocumentStreamData("Goodb");
+
+ // We've seen the header by now
+ VerifyHeaderComplete(1, false);
+
+ // Close the pull stream, this will transition back to push I/O.
+ http_stream->Close();
+ Thread::Current()->ProcessMessages(0);
+
+ // Remainder of document completed successfully
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+ VerifyDocumentContents("ye!");
+
+ talk_base::LogMessage::LogToDebug(old_sev);
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
+ // Queue response document
+ SetupSource(kHttpResponse);
+
+ // Switch to pull mode in response to header arrival
+ obtain_stream = true;
+
+ // Begin receive
+ base.recv(&data);
+
+ // We've already seen the header, but not data has arrived
+ VerifyHeaderComplete(1, false);
+ VerifyDocumentContents("");
+
+ // Pull the document data
+ ReadDocumentStreamData("Goodbye!");
+ VerifyDocumentStreamIsEOS();
+
+ // Document completed successfully
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+ VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
+ // Queue empty response document
+ SetupSource(kHttpEmptyResponse);
+
+ // Switch to pull mode in response to header arrival
+ obtain_stream = true;
+
+ // Begin receive
+ base.recv(&data);
+
+ // We've already seen the header, but not data has arrived
+ VerifyHeaderComplete(1, true);
+ VerifyDocumentContents("");
+
+ // The document is still open, until we attempt to read
+ ASSERT_TRUE(NULL != http_stream);
+ EXPECT_EQ(SS_OPEN, http_stream->GetState());
+
+ // Attempt to read data, and discover EOS
+ VerifyDocumentStreamIsEOS();
+
+ // Document completed successfully
+ VerifyTransferComplete(HM_RECV, HE_NONE);
+ VerifyDocumentContents("");
+}
+
+TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
+ // Switch to pull mode
+ ObtainDocumentStream();
+ VerifyDocumentStreamIsOpening();
+
+ // Queue response document
+ SetupSource(kHttpResponsePrefix);
+ VerifyDocumentStreamIsOpening();
+
+ // Begin receive
+ base.recv(&data);
+
+ // Pull document data
+ VerifyDocumentStreamOpenEvent();
+ ReadDocumentStreamData("Goodbye!");
+
+ // Simulate unexpected close
+ src.SetState(SS_CLOSED);
+
+ // Observe error event on document stream
+ EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
+
+ // Future reads give an error
+ int error = 0;
+ char buffer[5] = { 0 };
+ EXPECT_EQ(SR_ERROR, http_stream->Read(buffer, sizeof(buffer), NULL, &error));
+ EXPECT_EQ(HE_DISCONNECTED, error);
+
+ // Document completed with error
+ VerifyHeaderComplete(2, false);
+ VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
+ VerifyDocumentContents("");
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpcommon_unittest.cc b/talk/base/httpcommon_unittest.cc
new file mode 100644
index 0000000..37d77ed
--- /dev/null
+++ b/talk/base/httpcommon_unittest.cc
@@ -0,0 +1,162 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+#define TEST_PROTOCOL "http://"
+#define TEST_HOST "www.google.com"
+#define TEST_PATH "/folder/file.html"
+#define TEST_QUERY "?query=x&attr=y"
+#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY
+
+TEST(Url, DecomposesUrls) {
+ Url<char> url(TEST_URL);
+ EXPECT_TRUE(url.valid());
+ EXPECT_FALSE(url.secure());
+ EXPECT_STREQ(TEST_HOST, url.host().c_str());
+ EXPECT_EQ(80, url.port());
+ EXPECT_STREQ(TEST_PATH, url.path().c_str());
+ EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+ EXPECT_STREQ(TEST_HOST, url.address().c_str());
+ EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+ EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, ComposesUrls) {
+ // Set in constructor
+ Url<char> url(TEST_PATH TEST_QUERY, TEST_HOST, 80);
+ EXPECT_TRUE(url.valid());
+ EXPECT_FALSE(url.secure());
+ EXPECT_STREQ(TEST_HOST, url.host().c_str());
+ EXPECT_EQ(80, url.port());
+ EXPECT_STREQ(TEST_PATH, url.path().c_str());
+ EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+ EXPECT_STREQ(TEST_HOST, url.address().c_str());
+ EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+ EXPECT_STREQ(TEST_URL, url.url().c_str());
+
+ url.clear();
+ EXPECT_FALSE(url.valid());
+ EXPECT_FALSE(url.secure());
+ EXPECT_STREQ("", url.host().c_str());
+ EXPECT_EQ(80, url.port());
+ EXPECT_STREQ("/", url.path().c_str());
+ EXPECT_STREQ("", url.query().c_str());
+
+ // Set component-wise
+ url.set_host(TEST_HOST);
+ url.set_port(80);
+ url.set_path(TEST_PATH);
+ url.set_query(TEST_QUERY);
+ EXPECT_TRUE(url.valid());
+ EXPECT_FALSE(url.secure());
+ EXPECT_STREQ(TEST_HOST, url.host().c_str());
+ EXPECT_EQ(80, url.port());
+ EXPECT_STREQ(TEST_PATH, url.path().c_str());
+ EXPECT_STREQ(TEST_QUERY, url.query().c_str());
+ EXPECT_STREQ(TEST_HOST, url.address().c_str());
+ EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
+ EXPECT_STREQ(TEST_URL, url.url().c_str());
+}
+
+TEST(Url, EnsuresNonEmptyPath) {
+ Url<char> url(TEST_PROTOCOL TEST_HOST);
+ EXPECT_TRUE(url.valid());
+ EXPECT_STREQ("/", url.path().c_str());
+
+ url.clear();
+ EXPECT_STREQ("/", url.path().c_str());
+ url.set_path("");
+ EXPECT_STREQ("/", url.path().c_str());
+
+ url.clear();
+ EXPECT_STREQ("/", url.path().c_str());
+ url.set_full_path("");
+ EXPECT_STREQ("/", url.path().c_str());
+}
+
+TEST(Url, GetQueryAttributes) {
+ Url<char> url(TEST_URL);
+ std::string value;
+ EXPECT_TRUE(url.get_attribute("query", &value));
+ EXPECT_STREQ("x", value.c_str());
+ value.clear();
+ EXPECT_TRUE(url.get_attribute("attr", &value));
+ EXPECT_STREQ("y", value.c_str());
+ value.clear();
+ EXPECT_FALSE(url.get_attribute("Query", &value));
+ EXPECT_TRUE(value.empty());
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_0) {
+ static const char kResponseString[] = "HTTP/1.0 200 OK";
+ HttpResponseData response;
+ EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+ sizeof(kResponseString) - 1));
+ EXPECT_EQ(HVER_1_0, response.version);
+ EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttp1_1) {
+ static const char kResponseString[] = "HTTP/1.1 200 OK";
+ HttpResponseData response;
+ EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+ sizeof(kResponseString) - 1));
+ EXPECT_EQ(HVER_1_1, response.version);
+ EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpUnknown) {
+ static const char kResponseString[] = "HTTP 200 OK";
+ HttpResponseData response;
+ EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+ sizeof(kResponseString) - 1));
+ EXPECT_EQ(HVER_UNKNOWN, response.version);
+ EXPECT_EQ(200U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpFailure) {
+ static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable";
+ HttpResponseData response;
+ EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
+ sizeof(kResponseString) - 1));
+ EXPECT_EQ(HVER_1_1, response.version);
+ EXPECT_EQ(503U, response.scode);
+}
+
+TEST(HttpResponseData, parseLeaderHttpInvalid) {
+ static const char kResponseString[] = "Durrrrr, what's HTTP?";
+ HttpResponseData response;
+ EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString,
+ sizeof(kResponseString) - 1));
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpserver.cc b/talk/base/httpserver.cc
new file mode 100644
index 0000000..dd4898f
--- /dev/null
+++ b/talk/base/httpserver.cc
@@ -0,0 +1,293 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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 <algorithm>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::HttpServer() : next_connection_id_(1), closing_(false) {
+}
+
+HttpServer::~HttpServer() {
+ if (closing_) {
+ LOG(LS_WARNING) << "HttpServer::CloseAll has not completed";
+ }
+ for (ConnectionMap::iterator it = connections_.begin();
+ it != connections_.end();
+ ++it) {
+ StreamInterface* stream = it->second->EndProcess();
+ delete stream;
+ delete it->second;
+ }
+}
+
+int
+HttpServer::HandleConnection(StreamInterface* stream) {
+ int connection_id = next_connection_id_++;
+ ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID);
+ Connection* connection = new Connection(connection_id, this);
+ connections_.insert(ConnectionMap::value_type(connection_id, connection));
+ connection->BeginProcess(stream);
+ return connection_id;
+}
+
+void
+HttpServer::Respond(HttpServerTransaction* transaction) {
+ int connection_id = transaction->connection_id();
+ if (Connection* connection = Find(connection_id)) {
+ connection->Respond(transaction);
+ } else {
+ delete transaction;
+ // We may be tempted to SignalHttpComplete, but that implies that a
+ // connection still exists.
+ }
+}
+
+void
+HttpServer::Close(int connection_id, bool force) {
+ if (Connection* connection = Find(connection_id)) {
+ connection->InitiateClose(force);
+ }
+}
+
+void
+HttpServer::CloseAll(bool force) {
+ if (connections_.empty()) {
+ SignalCloseAllComplete(this);
+ return;
+ }
+ closing_ = true;
+ std::list<Connection*> connections;
+ for (ConnectionMap::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ connections.push_back(it->second);
+ }
+ for (std::list<Connection*>::const_iterator it = connections.begin();
+ it != connections.end(); ++it) {
+ (*it)->InitiateClose(force);
+ }
+}
+
+HttpServer::Connection*
+HttpServer::Find(int connection_id) {
+ ConnectionMap::iterator it = connections_.find(connection_id);
+ if (it == connections_.end())
+ return NULL;
+ return it->second;
+}
+
+void
+HttpServer::Remove(int connection_id) {
+ ConnectionMap::iterator it = connections_.find(connection_id);
+ if (it == connections_.end()) {
+ ASSERT(false);
+ return;
+ }
+ Connection* connection = it->second;
+ connections_.erase(it);
+ SignalConnectionClosed(this, connection_id, connection->EndProcess());
+ delete connection;
+ if (closing_ && connections_.empty()) {
+ closing_ = false;
+ SignalCloseAllComplete(this);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpServer::Connection
+///////////////////////////////////////////////////////////////////////////////
+
+HttpServer::Connection::Connection(int connection_id, HttpServer* server)
+ : connection_id_(connection_id), server_(server),
+ current_(NULL), signalling_(false), close_(false) {
+}
+
+HttpServer::Connection::~Connection() {
+ // It's possible that an object hosted inside this transaction signalled
+ // an event which caused the connection to close.
+ Thread::Current()->Dispose(current_);
+}
+
+void
+HttpServer::Connection::BeginProcess(StreamInterface* stream) {
+ base_.notify(this);
+ base_.attach(stream);
+ current_ = new HttpServerTransaction(connection_id_);
+ if (base_.mode() != HM_CONNECT)
+ base_.recv(¤t_->request);
+}
+
+StreamInterface*
+HttpServer::Connection::EndProcess() {
+ base_.notify(NULL);
+ base_.abort(HE_DISCONNECTED);
+ return base_.detach();
+}
+
+void
+HttpServer::Connection::Respond(HttpServerTransaction* transaction) {
+ ASSERT(current_ == NULL);
+ current_ = transaction;
+ if (current_->response.begin() == current_->response.end()) {
+ current_->response.set_error(HC_INTERNAL_SERVER_ERROR);
+ }
+ bool keep_alive = HttpShouldKeepAlive(current_->request);
+ current_->response.setHeader(HH_CONNECTION,
+ keep_alive ? "Keep-Alive" : "Close",
+ false);
+ close_ = !HttpShouldKeepAlive(current_->response);
+ base_.send(¤t_->response);
+}
+
+void
+HttpServer::Connection::InitiateClose(bool force) {
+ bool request_in_progress = (HM_SEND == base_.mode()) || (NULL == current_);
+ if (!signalling_ && (force || !request_in_progress)) {
+ server_->Remove(connection_id_);
+ } else {
+ close_ = true;
+ }
+}
+
+//
+// IHttpNotify Implementation
+//
+
+HttpError
+HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+ if (data_size == SIZE_UNKNOWN) {
+ data_size = 0;
+ }
+ ASSERT(current_ != NULL);
+ bool custom_document = false;
+ server_->SignalHttpRequestHeader(server_, current_, &custom_document);
+ if (!custom_document) {
+ current_->request.document.reset(new MemoryStream);
+ }
+ return HE_NONE;
+}
+
+void
+HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) {
+ if (mode == HM_SEND) {
+ ASSERT(current_ != NULL);
+ signalling_ = true;
+ server_->SignalHttpRequestComplete(server_, current_, err);
+ signalling_ = false;
+ if (close_) {
+ // Force a close
+ err = HE_DISCONNECTED;
+ }
+ }
+ if (err != HE_NONE) {
+ server_->Remove(connection_id_);
+ } else if (mode == HM_CONNECT) {
+ base_.recv(¤t_->request);
+ } else if (mode == HM_RECV) {
+ ASSERT(current_ != NULL);
+ // TODO: do we need this?
+ //request_.document_->rewind();
+ HttpServerTransaction* transaction = current_;
+ current_ = NULL;
+ server_->SignalHttpRequest(server_, transaction);
+ } else if (mode == HM_SEND) {
+ Thread::Current()->Dispose(current_->response.document.release());
+ current_->request.clear(true);
+ current_->response.clear(true);
+ base_.recv(¤t_->request);
+ } else {
+ ASSERT(false);
+ }
+}
+
+void
+HttpServer::Connection::onHttpClosed(HttpError err) {
+ UNUSED(err);
+ server_->Remove(connection_id_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpListenServer
+///////////////////////////////////////////////////////////////////////////////
+
+HttpListenServer::HttpListenServer()
+: listener_(Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+ listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent);
+ SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed);
+}
+
+HttpListenServer::~HttpListenServer() {
+}
+
+int HttpListenServer::Listen(const SocketAddress& address) {
+ if ((listener_->Bind(address) != SOCKET_ERROR) &&
+ (listener_->Listen(5) != SOCKET_ERROR))
+ return 0;
+ return listener_->GetError();
+}
+
+bool HttpListenServer::GetAddress(SocketAddress* address) const {
+ *address = listener_->GetLocalAddress();
+ return !address->IsNil();
+}
+
+void HttpListenServer::StopListening() {
+ listener_->Close();
+}
+
+void HttpListenServer::OnReadEvent(AsyncSocket* socket) {
+ ASSERT(socket == listener_.get());
+ AsyncSocket* incoming = listener_->Accept(NULL);
+ if (incoming) {
+ StreamInterface* stream = new SocketStream(incoming);
+ //stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false);
+ HandleConnection(stream);
+ }
+}
+
+void HttpListenServer::OnConnectionClosed(HttpServer* server,
+ int connection_id,
+ StreamInterface* stream) {
+ Thread::Current()->Dispose(stream);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/httpserver.h b/talk/base/httpserver.h
new file mode 100644
index 0000000..67061ee
--- /dev/null
+++ b/talk/base/httpserver.h
@@ -0,0 +1,154 @@
+/*
+ * libjingle
+ * Copyright 2004--2005, 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.
+ */
+
+#ifndef TALK_BASE_HTTPSERVER_H__
+#define TALK_BASE_HTTPSERVER_H__
+
+#include <map>
+#include "talk/base/httpbase.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class HttpServer;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// HttpServer
+//////////////////////////////////////////////////////////////////////
+
+const int HTTP_INVALID_CONNECTION_ID = 0;
+
+struct HttpServerTransaction : public HttpTransaction {
+public:
+ HttpServerTransaction(int id) : connection_id_(id) { }
+ int connection_id() const { return connection_id_; }
+
+private:
+ int connection_id_;
+};
+
+class HttpServer {
+public:
+ HttpServer();
+ virtual ~HttpServer();
+
+ int HandleConnection(StreamInterface* stream);
+ // Due to sigslot issues, we can't destroy some streams at an arbitrary time.
+ sigslot::signal3<HttpServer*, int, StreamInterface*> SignalConnectionClosed;
+
+ // This signal occurs when the HTTP request headers have been received, but
+ // before the request body is written to the request document. By default,
+ // the request document is a MemoryStream. By handling this signal, the
+ // document can be overridden, in which case the third signal argument should
+ // be set to true. In the case where the request body should be ignored,
+ // the document can be set to NULL. Note that the transaction object is still
+ // owened by the HttpServer at this point.
+ sigslot::signal3<HttpServer*, HttpServerTransaction*, bool*>
+ SignalHttpRequestHeader;
+
+ // An HTTP request has been made, and is available in the transaction object.
+ // Populate the transaction's response, and then return the object via the
+ // Respond method. Note that during this time, ownership of the transaction
+ // object is transferred, so it may be passed between threads, although
+ // respond must be called on the server's active thread.
+ sigslot::signal2<HttpServer*, HttpServerTransaction*> SignalHttpRequest;
+ void Respond(HttpServerTransaction* transaction);
+
+ // If you want to know when a request completes, listen to this event.
+ sigslot::signal3<HttpServer*, HttpServerTransaction*, int>
+ SignalHttpRequestComplete;
+
+ // Stop processing the connection indicated by connection_id.
+ // Unless force is true, the server will complete sending a response that is
+ // in progress.
+ void Close(int connection_id, bool force);
+ void CloseAll(bool force);
+
+ // After calling CloseAll, this event is signalled to indicate that all
+ // outstanding connections have closed.
+ sigslot::signal1<HttpServer*> SignalCloseAllComplete;
+
+private:
+ class Connection : private IHttpNotify {
+ public:
+ Connection(int connection_id, HttpServer* server);
+ virtual ~Connection();
+
+ void BeginProcess(StreamInterface* stream);
+ StreamInterface* EndProcess();
+
+ void Respond(HttpServerTransaction* transaction);
+ void InitiateClose(bool force);
+
+ // IHttpNotify Interface
+ virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+ virtual void onHttpComplete(HttpMode mode, HttpError err);
+ virtual void onHttpClosed(HttpError err);
+
+ int connection_id_;
+ HttpServer* server_;
+ HttpBase base_;
+ HttpServerTransaction* current_;
+ bool signalling_, close_;
+ };
+
+ Connection* Find(int connection_id);
+ void Remove(int connection_id);
+
+ friend class Connection;
+ typedef std::map<int,Connection*> ConnectionMap;
+
+ ConnectionMap connections_;
+ int next_connection_id_;
+ bool closing_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+class HttpListenServer : public HttpServer, public sigslot::has_slots<> {
+public:
+ HttpListenServer();
+ virtual ~HttpListenServer();
+
+ int Listen(const SocketAddress& address);
+ bool GetAddress(SocketAddress* address) const;
+ void StopListening();
+
+private:
+ void OnReadEvent(AsyncSocket* socket);
+ void OnConnectionClosed(HttpServer* server, int connection_id,
+ StreamInterface* stream);
+
+ scoped_ptr<AsyncSocket> listener_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPSERVER_H__
diff --git a/talk/base/httpserver_unittest.cc b/talk/base/httpserver_unittest.cc
new file mode 100644
index 0000000..d0e0760
--- /dev/null
+++ b/talk/base/httpserver_unittest.cc
@@ -0,0 +1,130 @@
+// Copyright 2007 Google Inc.
+// All Rights Reserved.
+
+
+#include "talk/base/gunit.h"
+#include "talk/base/httpserver.h"
+#include "talk/base/testutils.h"
+
+using namespace testing;
+
+namespace talk_base {
+
+namespace {
+ const char* const kRequest =
+ "GET /index.html HTTP/1.1\r\n"
+ "Host: localhost\r\n"
+ "\r\n";
+
+ const char* const kResponse =
+ "HTTP/1.1 200\r\n"
+ "Connection: Close\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n";
+
+ struct HttpServerMonitor : public sigslot::has_slots<> {
+ HttpServerTransaction* transaction;
+ bool server_closed, connection_closed;
+
+ HttpServerMonitor(HttpServer* server)
+ : transaction(NULL), server_closed(false), connection_closed(false) {
+ server->SignalCloseAllComplete.connect(this,
+ &HttpServerMonitor::OnClosed);
+ server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest);
+ server->SignalHttpRequestComplete.connect(this,
+ &HttpServerMonitor::OnRequestComplete);
+ server->SignalConnectionClosed.connect(this,
+ &HttpServerMonitor::OnConnectionClosed);
+ }
+ void OnRequest(HttpServer*, HttpServerTransaction* t) {
+ ASSERT_FALSE(transaction);
+ transaction = t;
+ transaction->response.set_success();
+ transaction->response.setHeader(HH_CONNECTION, "Close");
+ }
+ void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) {
+ ASSERT_EQ(transaction, t);
+ transaction = NULL;
+ }
+ void OnClosed(HttpServer*) {
+ server_closed = true;
+ }
+ void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) {
+ connection_closed = true;
+ delete stream;
+ }
+ };
+
+ void CreateClientConnection(HttpServer& server,
+ HttpServerMonitor& monitor,
+ bool send_request) {
+ StreamSource* client = new StreamSource;
+ client->SetState(SS_OPEN);
+ server.HandleConnection(client);
+ EXPECT_FALSE(monitor.server_closed);
+ EXPECT_FALSE(monitor.transaction);
+
+ if (send_request) {
+ // Simulate a request
+ client->QueueString(kRequest);
+ EXPECT_FALSE(monitor.server_closed);
+ }
+ }
+} // anonymous namespace
+
+TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) {
+ HttpServer server;
+ HttpServerMonitor monitor(&server);
+ // Add an active client connection
+ CreateClientConnection(server, monitor, true);
+ // Simulate a response
+ ASSERT_TRUE(NULL != monitor.transaction);
+ server.Respond(monitor.transaction);
+ EXPECT_FALSE(monitor.transaction);
+ // Connection has closed, but no server close signal
+ EXPECT_FALSE(monitor.server_closed);
+ EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) {
+ HttpServer server;
+ HttpServerMonitor monitor(&server);
+ // Add an idle client connection
+ CreateClientConnection(server, monitor, false);
+ // Perform graceful close
+ server.CloseAll(false);
+ // Connections have all closed
+ EXPECT_TRUE(monitor.server_closed);
+ EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) {
+ HttpServer server;
+ HttpServerMonitor monitor(&server);
+ // Add an active client connection
+ CreateClientConnection(server, monitor, true);
+ // Initiate a graceful close
+ server.CloseAll(false);
+ EXPECT_FALSE(monitor.server_closed);
+ // Simulate a response
+ ASSERT_TRUE(NULL != monitor.transaction);
+ server.Respond(monitor.transaction);
+ EXPECT_FALSE(monitor.transaction);
+ // Connections have all closed
+ EXPECT_TRUE(monitor.server_closed);
+ EXPECT_TRUE(monitor.connection_closed);
+}
+
+TEST(HttpServer, SignalsCloseAfterForcedCloseAll) {
+ HttpServer server;
+ HttpServerMonitor monitor(&server);
+ // Add an active client connection
+ CreateClientConnection(server, monitor, true);
+ // Initiate a forceful close
+ server.CloseAll(true);
+ // Connections have all closed
+ EXPECT_TRUE(monitor.server_closed);
+ EXPECT_TRUE(monitor.connection_closed);
+}
+
+} // namespace talk_base
diff --git a/talk/base/logging_unittest.cc b/talk/base/logging_unittest.cc
new file mode 100644
index 0000000..18de822
--- /dev/null
+++ b/talk/base/logging_unittest.cc
@@ -0,0 +1,142 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/fileutils.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// Test basic logging operation. We should get the INFO log but not the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, SingleStream) {
+ int sev = LogMessage::GetLogToStream(NULL);
+
+ std::string str;
+ StringStream stream(str);
+ LogMessage::AddLogToStream(&stream, LS_INFO);
+ EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream));
+
+ LOG(LS_INFO) << "INFO";
+ LOG(LS_VERBOSE) << "VERBOSE";
+ EXPECT_NE(std::string::npos, str.find("INFO"));
+ EXPECT_EQ(std::string::npos, str.find("VERBOSE"));
+
+ LogMessage::RemoveLogToStream(&stream);
+ EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream));
+
+ EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Test using multiple log streams. The INFO stream should get the INFO message,
+// the VERBOSE stream should get the INFO and the VERBOSE.
+// We should restore the correct global state at the end.
+TEST(LogTest, MultipleStreams) {
+ int sev = LogMessage::GetLogToStream(NULL);
+
+ std::string str1, str2;
+ StringStream stream1(str1), stream2(str2);
+ LogMessage::AddLogToStream(&stream1, LS_INFO);
+ LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+ EXPECT_EQ(LS_INFO, LogMessage::GetLogToStream(&stream1));
+ EXPECT_EQ(LS_VERBOSE, LogMessage::GetLogToStream(&stream2));
+
+ LOG(LS_INFO) << "INFO";
+ LOG(LS_VERBOSE) << "VERBOSE";
+
+ EXPECT_NE(std::string::npos, str1.find("INFO"));
+ EXPECT_EQ(std::string::npos, str1.find("VERBOSE"));
+ EXPECT_NE(std::string::npos, str2.find("INFO"));
+ EXPECT_NE(std::string::npos, str2.find("VERBOSE"));
+
+ LogMessage::RemoveLogToStream(&stream2);
+ LogMessage::RemoveLogToStream(&stream1);
+ EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream2));
+ EXPECT_EQ(LogMessage::NO_LOGGING, LogMessage::GetLogToStream(&stream1));
+
+ EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Ensure we don't crash when adding/removing streams while threads are going.
+// We should restore the correct global state at the end.
+class LogThread : public Thread {
+ void Run() {
+ // LS_SENSITIVE to avoid cluttering up any real logging going on
+ LOG(LS_SENSITIVE) << "LOG";
+ }
+};
+
+TEST(LogTest, MultipleThreads) {
+ int sev = LogMessage::GetLogToStream(NULL);
+
+ LogThread thread1, thread2, thread3;
+ thread1.Start();
+ thread2.Start();
+ thread3.Start();
+
+ NullStream stream1, stream2, stream3;
+ for (int i = 0; i < 1000; ++i) {
+ LogMessage::AddLogToStream(&stream1, LS_INFO);
+ LogMessage::AddLogToStream(&stream2, LS_VERBOSE);
+ LogMessage::AddLogToStream(&stream3, LS_SENSITIVE);
+ LogMessage::RemoveLogToStream(&stream1);
+ LogMessage::RemoveLogToStream(&stream2);
+ LogMessage::RemoveLogToStream(&stream3);
+ }
+
+ EXPECT_EQ(sev, LogMessage::GetLogToStream(NULL));
+}
+
+// Test the time required to write 1000 80-character logs to an unbuffered file.
+TEST(LogTest, Perf) {
+ Pathname path;
+ EXPECT_TRUE(Filesystem::GetTemporaryFolder(path, true, NULL));
+ path.SetPathname(Filesystem::TempFilename(path, "ut"));
+
+ FileStream stream;
+ EXPECT_TRUE(stream.Open(path.pathname(), "wb", NULL));
+ stream.DisableBuffering();
+ LogMessage::AddLogToStream(&stream, LS_SENSITIVE);
+
+ uint32 start = Time(), finish;
+ std::string message('X', 80);
+ for (int i = 0; i < 1000; ++i) {
+ LOG(LS_SENSITIVE) << message;
+ }
+ finish = Time();
+
+ LogMessage::RemoveLogToStream(&stream);
+ stream.Close();
+ Filesystem::DeleteFile(path);
+
+ LOG(LS_INFO) << "Average log time: " << TimeDiff(finish, start) << " us";
+}
+
+} // namespace talk_base
diff --git a/talk/base/messagequeue_unittest.cc b/talk/base/messagequeue_unittest.cc
new file mode 100644
index 0000000..7414618
--- /dev/null
+++ b/talk/base/messagequeue_unittest.cc
@@ -0,0 +1,53 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/time.h"
+#include "talk/base/messagequeue.h"
+
+using namespace talk_base;
+
+TEST(MessageQueue, DelayedPostsWithIdenticalTimesAreProcessedInFifoOrder) {
+ MessageQueue q;
+
+ TimeStamp now = Time();
+ q.PostAt(now, NULL, 3);
+ q.PostAt(now - 2, NULL, 0);
+ q.PostAt(now - 1, NULL, 1);
+ q.PostAt(now, NULL, 4);
+ q.PostAt(now - 1, NULL, 2);
+
+ Message msg;
+ for (size_t i=0; i<5; ++i) {
+ memset(&msg, 0, sizeof(msg));
+ EXPECT_TRUE(q.Get(&msg, 0));
+ EXPECT_EQ(i, msg.message_id);
+ }
+
+ EXPECT_FALSE(q.Get(&msg, 0)); // No more messages
+}
diff --git a/talk/base/network_unittest.cc b/talk/base/network_unittest.cc
index 6a9919d..a55ba2f 100644
--- a/talk/base/network_unittest.cc
+++ b/talk/base/network_unittest.cc
@@ -1,7 +1,29 @@
-// Copyright 2009 Google Inc.
-// All Rights Reserved.
-//
-
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 <vector>
#include "talk/base/gunit.h"
diff --git a/talk/base/referencecountedsingletonfactory_unittest.cc b/talk/base/referencecountedsingletonfactory_unittest.cc
new file mode 100644
index 0000000..3fc7fd2
--- /dev/null
+++ b/talk/base/referencecountedsingletonfactory_unittest.cc
@@ -0,0 +1,149 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/referencecountedsingletonfactory.h"
+
+namespace talk_base {
+
+class MyExistenceWatcher {
+ public:
+ MyExistenceWatcher() { create_called_ = true; }
+ ~MyExistenceWatcher() { delete_called_ = true; }
+
+ static bool create_called_;
+ static bool delete_called_;
+};
+
+bool MyExistenceWatcher::create_called_ = false;
+bool MyExistenceWatcher::delete_called_ = false;
+
+class TestReferenceCountedSingletonFactory :
+ public ReferenceCountedSingletonFactory<MyExistenceWatcher> {
+ protected:
+ virtual bool SetupInstance() {
+ instance_.reset(new MyExistenceWatcher());
+ return true;
+ }
+
+ virtual void CleanupInstance() {
+ instance_.reset();
+ }
+};
+
+static void DoCreateAndGoOutOfScope(
+ ReferenceCountedSingletonFactory<MyExistenceWatcher> *factory) {
+ rcsf_ptr<MyExistenceWatcher> ptr(factory);
+ ptr.get();
+ // and now ptr should go out of scope.
+}
+
+TEST(ReferenceCountedSingletonFactory, ZeroReferenceCountCausesDeletion) {
+ TestReferenceCountedSingletonFactory factory;
+ MyExistenceWatcher::delete_called_ = false;
+ DoCreateAndGoOutOfScope(&factory);
+ EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, NonZeroReferenceCountDoesNotDelete) {
+ TestReferenceCountedSingletonFactory factory;
+ rcsf_ptr<MyExistenceWatcher> ptr(&factory);
+ ptr.get();
+ MyExistenceWatcher::delete_called_ = false;
+ DoCreateAndGoOutOfScope(&factory);
+ EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, ReturnedPointersReferToSameThing) {
+ TestReferenceCountedSingletonFactory factory;
+ rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+ EXPECT_EQ(one.get(), two.get());
+}
+
+TEST(ReferenceCountedSingletonFactory, Release) {
+ TestReferenceCountedSingletonFactory factory;
+
+ rcsf_ptr<MyExistenceWatcher> one(&factory);
+ one.get();
+
+ MyExistenceWatcher::delete_called_ = false;
+ one.release();
+ EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetWithoutRelease) {
+ TestReferenceCountedSingletonFactory factory;
+ rcsf_ptr<MyExistenceWatcher> one(&factory);
+ one.get();
+
+ MyExistenceWatcher::create_called_ = false;
+ one.get();
+ EXPECT_FALSE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, GetAfterRelease) {
+ TestReferenceCountedSingletonFactory factory;
+ rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+ MyExistenceWatcher::create_called_ = false;
+ one.release();
+ one.get();
+ EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, MultipleReleases) {
+ TestReferenceCountedSingletonFactory factory;
+ rcsf_ptr<MyExistenceWatcher> one(&factory), two(&factory);
+
+ MyExistenceWatcher::create_called_ = false;
+ MyExistenceWatcher::delete_called_ = false;
+ one.release();
+ EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+ one.release();
+ EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+ one.release();
+ EXPECT_FALSE(MyExistenceWatcher::delete_called_);
+ one.get();
+ EXPECT_TRUE(MyExistenceWatcher::create_called_);
+}
+
+TEST(ReferenceCountedSingletonFactory, Existentialism) {
+ TestReferenceCountedSingletonFactory factory;
+
+ rcsf_ptr<MyExistenceWatcher> one(&factory);
+
+ MyExistenceWatcher::create_called_ = false;
+ MyExistenceWatcher::delete_called_ = false;
+
+ one.get();
+ EXPECT_TRUE(MyExistenceWatcher::create_called_);
+ one.release();
+ EXPECT_TRUE(MyExistenceWatcher::delete_called_);
+}
+
+} // namespace talk_base
diff --git a/talk/base/signalthread_unittest.cc b/talk/base/signalthread_unittest.cc
new file mode 100644
index 0000000..91665aa
--- /dev/null
+++ b/talk/base/signalthread_unittest.cc
@@ -0,0 +1,202 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/thread.h"
+
+using namespace talk_base;
+
+class SignalThreadTest : public testing::Test, public sigslot::has_slots<> {
+ public:
+ class SlowSignalThread : public SignalThread {
+ public:
+ SlowSignalThread(SignalThreadTest* harness) : harness_(harness) {
+ }
+
+ virtual ~SlowSignalThread() {
+ EXPECT_EQ(harness_->main_thread_, Thread::Current());
+ ++harness_->thread_deleted_;
+ }
+
+ const SignalThreadTest* harness() { return harness_; }
+
+ protected:
+ virtual void OnWorkStart() {
+ ASSERT_TRUE(harness_ != NULL);
+ ++harness_->thread_started_;
+ EXPECT_EQ(harness_->main_thread_, Thread::Current());
+ EXPECT_FALSE(worker()->started()); // not started yet
+ }
+
+ virtual void OnWorkStop() {
+ ++harness_->thread_stopped_;
+ EXPECT_EQ(harness_->main_thread_, Thread::Current());
+ EXPECT_TRUE(worker()->started()); // not stopped yet
+ }
+
+ virtual void OnWorkDone() {
+ ++harness_->thread_done_;
+ EXPECT_EQ(harness_->main_thread_, Thread::Current());
+ EXPECT_TRUE(worker()->started()); // not stopped yet
+ }
+
+ virtual void DoWork() {
+ EXPECT_NE(harness_->main_thread_, Thread::Current());
+ EXPECT_EQ(worker(), Thread::Current());
+ Thread::Current()->socketserver()->Wait(250, false);
+ }
+
+ private:
+ SignalThreadTest* harness_;
+ DISALLOW_EVIL_CONSTRUCTORS(SlowSignalThread);
+ };
+
+ void OnWorkComplete(talk_base::SignalThread* thread) {
+ SlowSignalThread* t = static_cast<SlowSignalThread*>(thread);
+ EXPECT_EQ(t->harness(), this);
+ EXPECT_EQ(main_thread_, Thread::Current());
+
+ ++thread_completed_;
+ if (!called_release_) {
+ thread->Release();
+ }
+ }
+
+ virtual void SetUp() {
+ main_thread_ = Thread::Current();
+ thread_ = new SlowSignalThread(this);
+ thread_->SignalWorkDone.connect(this, &SignalThreadTest::OnWorkComplete);
+ called_release_ = false;
+ thread_started_ = 0;
+ thread_done_ = 0;
+ thread_completed_ = 0;
+ thread_stopped_ = 0;
+ thread_deleted_ = 0;
+ }
+
+ virtual void TearDown() {
+ }
+
+ Thread* main_thread_;
+ SlowSignalThread* thread_;
+ bool called_release_;
+
+ int thread_started_;
+ int thread_done_;
+ int thread_completed_;
+ int thread_stopped_;
+ int thread_deleted_;
+};
+
+class OwnerThread : public Thread, public sigslot::has_slots<> {
+ public:
+ OwnerThread(SignalThreadTest* harness) : harness_(harness) {
+ }
+
+ virtual void Run() {
+ SignalThreadTest::SlowSignalThread* signal_thread =
+ new SignalThreadTest::SlowSignalThread(harness_);
+ signal_thread->SignalWorkDone.connect(this, &OwnerThread::OnWorkDone);
+ signal_thread->Start();
+ Thread::Current()->socketserver()->Wait(100, false);
+ signal_thread->Release();
+ }
+
+ void OnWorkDone(SignalThread* signal_thread) {
+ FAIL() << " This shouldn't get called.";
+ }
+
+ private:
+ SignalThreadTest* harness_;
+ DISALLOW_EVIL_CONSTRUCTORS(OwnerThread);
+};
+
+// Test for when the main thread goes away while the
+// signal thread is still working. This may happen
+// when shutting down the process.
+TEST_F(SignalThreadTest, OwnerThreadGoesAway) {
+ {
+ scoped_ptr<OwnerThread> owner(new OwnerThread(this));
+ main_thread_ = owner.get();
+ owner->Start();
+ Thread::Current()->socketserver()->Wait(200, false);
+ }
+ // At this point the main thread has gone away.
+ // Give the SignalThread a little time to do its callback,
+ // which will crash if the signal thread doesn't handle
+ // this situation well.
+ Thread::Current()->socketserver()->Wait(500, false);
+}
+
+#define EXPECT_STATE(started, done, completed, stopped, deleted) \
+ EXPECT_EQ(started, thread_started_); \
+ EXPECT_EQ(done, thread_done_); \
+ EXPECT_EQ(completed, thread_completed_); \
+ EXPECT_EQ(stopped, thread_stopped_); \
+ EXPECT_EQ(deleted, thread_deleted_);
+
+TEST_F(SignalThreadTest, ThreadFinishes) {
+ thread_->Start();
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ Thread::SleepMs(500);
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, ReleasedThreadFinishes) {
+ thread_->Start();
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ thread_->Release();
+ called_release_ = true;
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ Thread::SleepMs(500);
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_STATE(1, 1, 1, 0, 1);
+}
+
+TEST_F(SignalThreadTest, DestroyedThreadCleansUp) {
+ thread_->Start();
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ thread_->Destroy(true);
+ EXPECT_STATE(1, 0, 0, 1, 1);
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_STATE(1, 0, 0, 1, 1);
+}
+
+TEST_F(SignalThreadTest, DeferredDestroyedThreadCleansUp) {
+ thread_->Start();
+ EXPECT_STATE(1, 0, 0, 0, 0);
+ thread_->Destroy(false);
+ EXPECT_STATE(1, 0, 0, 1, 0);
+ Thread::SleepMs(500);
+ EXPECT_STATE(1, 0, 0, 1, 0);
+ Thread::Current()->ProcessMessages(0);
+ EXPECT_STATE(1, 1, 0, 1, 1);
+}
diff --git a/talk/base/socketaddress_unittest.cc b/talk/base/socketaddress_unittest.cc
new file mode 100644
index 0000000..95b0071
--- /dev/null
+++ b/talk/base/socketaddress_unittest.cc
@@ -0,0 +1,209 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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.
+ */
+
+#ifdef POSIX
+#include <netinet/in.h> // for sockaddr_in
+#endif
+
+#include "talk/base/gunit.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+TEST(SocketAddressTest, TestDefaultCtor) {
+ SocketAddress addr;
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(0, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("0.0.0.0:0", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestIPPortCtor) {
+ SocketAddress addr(0x01020304, 5678);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestStringPortCtor) {
+ SocketAddress addr("1.2.3.4", 5678);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("1.2.3.4", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSpecialStringPortCtor) {
+ // inet_addr doesn't handle this address properly.
+ SocketAddress addr("255.255.255.255", 5678);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0xFFFFFFFFU, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("255.255.255.255", addr.hostname());
+ EXPECT_EQ("255.255.255.255:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestHostnamePortCtor) {
+ SocketAddress addr("a.b.com", 5678);
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("a.b.com", addr.hostname());
+ EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestCopyCtor) {
+ SocketAddress from("1.2.3.4", 5678);
+ SocketAddress addr(from);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("1.2.3.4", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestAssign) {
+ SocketAddress from("1.2.3.4", 5678);
+ SocketAddress addr(0x88888888, 9999);
+ addr = from;
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("1.2.3.4", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPPort) {
+ SocketAddress addr(0x88888888, 9999);
+ addr.SetIP(0x01020304);
+ addr.SetPort(5678);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPFromString) {
+ SocketAddress addr(0x88888888, 9999);
+ addr.SetIP("1.2.3.4");
+ addr.SetPort(5678);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("1.2.3.4", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestSetIPFromHostname) {
+ SocketAddress addr(0x88888888, 9999);
+ addr.SetIP("a.b.com");
+ addr.SetPort(5678);
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("a.b.com", addr.hostname());
+ EXPECT_EQ("a.b.com:5678", addr.ToString());
+ addr.SetResolvedIP(0x01020304);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ("a.b.com", addr.hostname());
+ EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestFromString) {
+ SocketAddress addr;
+ EXPECT_TRUE(addr.FromString("1.2.3.4:5678"));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("1.2.3.4", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestFromHostname) {
+ SocketAddress addr;
+ EXPECT_TRUE(addr.FromString("a.b.com:5678"));
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("a.b.com", addr.hostname());
+ EXPECT_EQ("a.b.com:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromSockAddr) {
+ SocketAddress from("1.2.3.4", 5678), addr;
+ sockaddr_in addr_in;
+ from.ToSockAddr(&addr_in);
+ EXPECT_TRUE(addr.FromSockAddr(addr_in));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestToFromBuffer) {
+ SocketAddress from("1.2.3.4", 5678), addr;
+ char buf[8];
+ EXPECT_TRUE(from.Write_(buf, sizeof(buf)));
+ EXPECT_TRUE(addr.Read_(buf, sizeof(buf)));
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_EQ(0x01020304U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("", addr.hostname());
+ EXPECT_EQ("1.2.3.4:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestGoodResolve) {
+ SocketAddress addr("localhost", 5678);
+ int error;
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+ EXPECT_TRUE(addr.ResolveIP(false, &error));
+ EXPECT_EQ(0, error);
+ EXPECT_FALSE(addr.IsUnresolvedIP());
+ EXPECT_TRUE(addr.IsLoopbackIP());
+ EXPECT_EQ(0x7F000001U, addr.ip());
+ EXPECT_EQ(5678, addr.port());
+ EXPECT_EQ("localhost", addr.hostname());
+ EXPECT_EQ("localhost:5678", addr.ToString());
+}
+
+TEST(SocketAddressTest, TestBadResolve) {
+ SocketAddress addr("address.bad", 5678);
+ int error;
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+ EXPECT_FALSE(addr.ResolveIP(false, &error));
+ EXPECT_NE(0, error);
+ EXPECT_TRUE(addr.IsUnresolvedIP());
+}
+
+} // namespace talk_base
diff --git a/talk/base/stream_unittest.cc b/talk/base/stream_unittest.cc
new file mode 100644
index 0000000..d0c5718
--- /dev/null
+++ b/talk/base/stream_unittest.cc
@@ -0,0 +1,445 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// TestStream
+///////////////////////////////////////////////////////////////////////////////
+
+class TestStream : public StreamInterface {
+public:
+ TestStream() : pos_(0) { }
+
+ virtual StreamState GetState() const { return SS_OPEN; }
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ unsigned char* uc_buffer = static_cast<unsigned char*>(buffer);
+ for (size_t i=0; i<buffer_len; ++i) {
+ uc_buffer[i] = pos_++;
+ }
+ if (read)
+ *read = buffer_len;
+ return SR_SUCCESS;
+ }
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (error)
+ *error = -1;
+ return SR_ERROR;
+ }
+ virtual void Close() { }
+ virtual bool SetPosition(size_t position) {
+ pos_ = position;
+ return true;
+ }
+ virtual bool GetPosition(size_t* position) const {
+ if (position) *position = pos_;
+ return true;
+ }
+ virtual bool GetSize(size_t* size) const {
+ return false;
+ }
+ virtual bool GetAvailable(size_t* size) const {
+ return false;
+ }
+
+private:
+ unsigned char pos_;
+};
+
+bool VerifyTestBuffer(unsigned char* buffer, size_t len,
+ unsigned char value) {
+ bool passed = true;
+ for (size_t i=0; i<len; ++i) {
+ if (buffer[i] != value++) {
+ passed = false;
+ break;
+ }
+ }
+ // Ensure that we don't pass again without re-writing
+ memset(buffer, 0, len);
+ return passed;
+}
+
+void SeekTest(StreamInterface* stream, const unsigned char value) {
+ size_t bytes;
+ unsigned char buffer[13] = { 0 };
+ const size_t kBufSize = sizeof(buffer);
+
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(bytes, kBufSize);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value));
+ EXPECT_TRUE(stream->GetPosition(&bytes));
+ EXPECT_EQ(13U, bytes);
+
+ EXPECT_TRUE(stream->SetPosition(7));
+
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(bytes, kBufSize);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, value + 7));
+ EXPECT_TRUE(stream->GetPosition(&bytes));
+ EXPECT_EQ(20U, bytes);
+}
+
+TEST(StreamSegment, TranslatesPosition) {
+ TestStream* test = new TestStream;
+ // Verify behavior of original stream
+ SeekTest(test, 0);
+ StreamSegment* segment = new StreamSegment(test);
+ // Verify behavior of adapted stream (all values offset by 20)
+ SeekTest(segment, 20);
+ delete segment;
+}
+
+TEST(StreamSegment, SupportsArtificialTermination) {
+ TestStream* test = new TestStream;
+
+ size_t bytes;
+ unsigned char buffer[5000] = { 0 };
+ const size_t kBufSize = sizeof(buffer);
+
+ {
+ StreamInterface* stream = test;
+
+ // Read a lot of bytes
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(bytes, kBufSize);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 0));
+
+ // Test seeking far ahead
+ EXPECT_TRUE(stream->SetPosition(12345));
+
+ // Read a bunch more bytes
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(bytes, kBufSize);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, kBufSize, 12345 % 256));
+ }
+
+ // Create a segment of test stream in range [100,600)
+ EXPECT_TRUE(test->SetPosition(100));
+ StreamSegment* segment = new StreamSegment(test, 500);
+
+ {
+ StreamInterface* stream = segment;
+
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(500U, bytes);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+ // Test seeking past "end" of stream
+ EXPECT_FALSE(stream->SetPosition(12345));
+ EXPECT_FALSE(stream->SetPosition(501));
+
+ // Test seeking to end (edge case)
+ EXPECT_TRUE(stream->SetPosition(500));
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+
+ // Test seeking to start
+ EXPECT_TRUE(stream->SetPosition(0));
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_SUCCESS);
+ EXPECT_EQ(500U, bytes);
+ EXPECT_TRUE(VerifyTestBuffer(buffer, 500, 100));
+ EXPECT_EQ(stream->Read(buffer, kBufSize, &bytes, NULL), SR_EOS);
+ }
+
+ delete segment;
+}
+
+TEST(FifoBufferTest, TestAll) {
+ const size_t kSize = 16;
+ const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+ char out[kSize * 2];
+ void* p;
+ const void* q;
+ size_t bytes;
+ FifoBuffer buf(kSize);
+ StreamInterface* stream = &buf;
+
+ // Test assumptions about base state
+ EXPECT_EQ(SS_OPEN, stream->GetState());
+ EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+ EXPECT_TRUE(NULL != stream->GetReadData(&bytes));
+ EXPECT_EQ((size_t)0, bytes);
+ stream->ConsumeReadData(0);
+ EXPECT_TRUE(NULL != stream->GetWriteBuffer(&bytes));
+ EXPECT_EQ(kSize, bytes);
+ stream->ConsumeWriteBuffer(0);
+
+ // Try a full write
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+
+ // Try a write that should block
+ EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+
+ // Try a full read
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize));
+
+ // Try a read that should block
+ EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+ // Try a too-big write
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 2, &bytes, NULL));
+ EXPECT_EQ(bytes, kSize);
+
+ // Try a too-big read
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize));
+
+ // Try some small writes and reads
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+ // Try wraparound reads and writes in the following pattern
+ // WWWWWWWWWWWW.... 0123456789AB....
+ // RRRRRRRRXXXX.... ........89AB....
+ // WWWW....XXXXWWWW 4567....89AB0123
+ // XXXX....RRRRXXXX 4567........0123
+ // XXXXWWWWWWWWXXXX 4567012345670123
+ // RRRRXXXXXXXXRRRR ....01234567....
+ // ....RRRRRRRR.... ................
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+ EXPECT_EQ(kSize * 3 / 4, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 4, &bytes, NULL));
+ EXPECT_EQ(kSize / 4 , bytes);
+ EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2 , bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(kSize / 2 , bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+
+ // Use GetWriteBuffer to reset the read_position for the next tests
+ stream->GetWriteBuffer(&bytes);
+ stream->ConsumeWriteBuffer(0);
+
+ // Try using GetReadData to do a full read
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ q = stream->GetReadData(&bytes);
+ EXPECT_TRUE(NULL != q);
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(q, in, kSize));
+ stream->ConsumeReadData(kSize);
+ EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+ // Try using GetReadData to do some small reads
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ q = stream->GetReadData(&bytes);
+ EXPECT_TRUE(NULL != q);
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+ stream->ConsumeReadData(kSize / 2);
+ q = stream->GetReadData(&bytes);
+ EXPECT_TRUE(NULL != q);
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(q, in + kSize / 2, kSize / 2));
+ stream->ConsumeReadData(kSize / 2);
+ EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+ // Try using GetReadData in a wraparound case
+ // WWWWWWWWWWWWWWWW 0123456789ABCDEF
+ // RRRRRRRRRRRRXXXX ............CDEF
+ // WWWWWWWW....XXXX 01234567....CDEF
+ // ............RRRR 01234567........
+ // RRRRRRRR........ ................
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ q = stream->GetReadData(&bytes);
+ EXPECT_TRUE(NULL != q);
+ EXPECT_EQ(kSize / 4, bytes);
+ EXPECT_EQ(0, memcmp(q, in + kSize * 3 / 4, kSize / 4));
+ stream->ConsumeReadData(kSize / 4);
+ q = stream->GetReadData(&bytes);
+ EXPECT_TRUE(NULL != q);
+ EXPECT_EQ(kSize / 2, bytes);
+ EXPECT_EQ(0, memcmp(q, in, kSize / 2));
+ stream->ConsumeReadData(kSize / 2);
+
+ // Use GetWriteBuffer to reset the read_position for the next tests
+ stream->GetWriteBuffer(&bytes);
+ stream->ConsumeWriteBuffer(0);
+
+ // Try using GetWriteBuffer to do a full write
+ p = stream->GetWriteBuffer(&bytes);
+ EXPECT_TRUE(NULL != p);
+ EXPECT_EQ(kSize, bytes);
+ memcpy(p, in, kSize);
+ stream->ConsumeWriteBuffer(kSize);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize));
+
+ // Try using GetWriteBuffer to do some small writes
+ p = stream->GetWriteBuffer(&bytes);
+ EXPECT_TRUE(NULL != p);
+ EXPECT_EQ(kSize, bytes);
+ memcpy(p, in, kSize / 2);
+ stream->ConsumeWriteBuffer(kSize / 2);
+ p = stream->GetWriteBuffer(&bytes);
+ EXPECT_TRUE(NULL != p);
+ EXPECT_EQ(kSize / 2, bytes);
+ memcpy(p, in + kSize / 2, kSize / 2);
+ stream->ConsumeWriteBuffer(kSize / 2);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize));
+
+ // Try using GetWriteBuffer in a wraparound case
+ // WWWWWWWWWWWW.... 0123456789AB....
+ // RRRRRRRRXXXX.... ........89AB....
+ // ........XXXXWWWW ........89AB0123
+ // WWWW....XXXXXXXX 4567....89AB0123
+ // RRRR....RRRRRRRR ................
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize * 3 / 4, &bytes, NULL));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ p = stream->GetWriteBuffer(&bytes);
+ EXPECT_TRUE(NULL != p);
+ EXPECT_EQ(kSize / 4, bytes);
+ memcpy(p, in, kSize / 4);
+ stream->ConsumeWriteBuffer(kSize / 4);
+ p = stream->GetWriteBuffer(&bytes);
+ EXPECT_TRUE(NULL != p);
+ EXPECT_EQ(kSize / 2, bytes);
+ memcpy(p, in + kSize / 4, kSize / 4);
+ stream->ConsumeWriteBuffer(kSize / 4);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 3 / 4, &bytes, NULL));
+ EXPECT_EQ(kSize * 3 / 4, bytes);
+ EXPECT_EQ(0, memcmp(in + kSize / 2, out, kSize / 4));
+ EXPECT_EQ(0, memcmp(in, out + kSize / 4, kSize / 4));
+
+ // Check that the stream is now empty
+ EXPECT_EQ(SR_BLOCK, stream->Read(out, kSize, &bytes, NULL));
+
+ // Try growing the buffer
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_TRUE(buf.SetCapacity(kSize * 2));
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in + kSize, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize * 2, &bytes, NULL));
+ EXPECT_EQ(kSize * 2, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize * 2));
+
+ // Try shrinking the buffer
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_TRUE(buf.SetCapacity(kSize));
+ EXPECT_EQ(SR_BLOCK, stream->Write(in, kSize, &bytes, NULL));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize, &bytes, NULL));
+ EXPECT_EQ(kSize, bytes);
+ EXPECT_EQ(0, memcmp(in, out, kSize));
+
+ // Write to the stream, close it, read the remaining bytes
+ EXPECT_EQ(SR_SUCCESS, stream->Write(in, kSize / 2, &bytes, NULL));
+ stream->Close();
+ EXPECT_EQ(SS_CLOSED, stream->GetState());
+ EXPECT_EQ(SR_EOS, stream->Write(in, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(SR_SUCCESS, stream->Read(out, kSize / 2, &bytes, NULL));
+ EXPECT_EQ(0, memcmp(in, out, kSize / 2));
+ EXPECT_EQ(SR_EOS, stream->Read(out, kSize / 2, &bytes, NULL));
+}
+
+TEST(FifoBufferTest, WriteOffsetAndReadOffset) {
+ const size_t kSize = 16;
+ const char in[kSize * 2 + 1] = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
+ char out[kSize * 2];
+ FifoBuffer buf(kSize);
+
+ // Write 14 bytes.
+ EXPECT_EQ(SR_SUCCESS, buf.Write(in, 14, NULL, NULL));
+
+ // Make sure data is in |buf|.
+ size_t buffered;
+ EXPECT_TRUE(buf.GetBuffered(&buffered));
+ EXPECT_EQ(14u, buffered);
+
+ // Read 10 bytes.
+ buf.ConsumeReadData(10);
+
+ // There should be now 12 bytes of available space.
+ size_t remaining;
+ EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+ EXPECT_EQ(12u, remaining);
+
+ // Write at offset 12, this should fail.
+ EXPECT_EQ(SR_BLOCK, buf.WriteOffset(in, 10, 12, NULL));
+
+ // Write 8 bytes at offset 4, this wraps around the buffer.
+ EXPECT_EQ(SR_SUCCESS, buf.WriteOffset(in, 8, 4, NULL));
+
+ // Number of available space remains the same until we call
+ // ConsumeWriteBuffer().
+ EXPECT_TRUE(buf.GetWriteRemaining(&remaining));
+ EXPECT_EQ(12u, remaining);
+ buf.ConsumeWriteBuffer(12);
+
+ // There's 4 bytes bypassed and 4 bytes no read so skip them and verify the
+ // 8 bytes written.
+ size_t read;
+ EXPECT_EQ(SR_SUCCESS, buf.ReadOffset(out, 8, 8, &read));
+ EXPECT_EQ(8u, read);
+ EXPECT_EQ(0, memcmp(out, in, 8));
+
+ // There should still be 16 bytes available for reading.
+ EXPECT_TRUE(buf.GetBuffered(&buffered));
+ EXPECT_EQ(16u, buffered);
+
+ // Read at offset 16, this should fail since we don't have that much data.
+ EXPECT_EQ(SR_BLOCK, buf.ReadOffset(out, 10, 16, NULL));
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringencode_unittest.cc b/talk/base/stringencode_unittest.cc
new file mode 100644
index 0000000..fc1f481
--- /dev/null
+++ b/talk/base/stringencode_unittest.cc
@@ -0,0 +1,204 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+TEST(utf8_encode, EncodeDecode) {
+ const struct Utf8Test {
+ const char* encoded;
+ size_t encsize, enclen;
+ unsigned long decoded;
+ } kTests[] = {
+ { "a ", 5, 1, 'a' },
+ { "\x7F ", 5, 1, 0x7F },
+ { "\xC2\x80 ", 5, 2, 0x80 },
+ { "\xDF\xBF ", 5, 2, 0x7FF },
+ { "\xE0\xA0\x80 ", 5, 3, 0x800 },
+ { "\xEF\xBF\xBF ", 5, 3, 0xFFFF },
+ { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 },
+ { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 },
+ { "\xF0\xF0\x80\x80 ", 5, 0, 0 },
+ { "\xF0\x90\x80 ", 5, 0, 0 },
+ { "\x90\x80\x80 ", 5, 0, 0 },
+ { NULL, 0, 0 },
+ };
+ for (size_t i=0; kTests[i].encoded; ++i) {
+ unsigned long val = 0;
+ ASSERT_EQ(kTests[i].enclen, utf8_decode(kTests[i].encoded,
+ kTests[i].encsize,
+ &val));
+ unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded;
+ ASSERT_EQ(result, val);
+
+ if (kTests[i].decoded == 0) {
+ // Not an interesting encoding test case
+ continue;
+ }
+
+ char buffer[5];
+ memset(buffer, 0x01, ARRAY_SIZE(buffer));
+ ASSERT_EQ(kTests[i].enclen, utf8_encode(buffer,
+ kTests[i].encsize,
+ kTests[i].decoded));
+ ASSERT_TRUE(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0);
+ // Make sure remainder of buffer is unchanged
+ ASSERT_TRUE(memory_check(buffer + kTests[i].enclen,
+ 0x1,
+ ARRAY_SIZE(buffer) - kTests[i].enclen));
+ }
+}
+
+// TODO: hex_encode unittest
+
+// Tests counting substrings.
+TEST(tokenizeTest, CountSubstrings) {
+ std::vector<std::string> fields;
+
+ EXPECT_EQ(5ul, tokenize("one two three four five", ' ', &fields));
+ fields.clear();
+ EXPECT_EQ(1ul, tokenize("one", ' ', &fields));
+
+ // Extra spaces should be ignored.
+ fields.clear();
+ EXPECT_EQ(5ul, tokenize(" one two three four five ", ' ', &fields));
+ fields.clear();
+ EXPECT_EQ(1ul, tokenize(" one ", ' ', &fields));
+ fields.clear();
+ EXPECT_EQ(0ul, tokenize(" ", ' ', &fields));
+}
+
+// Tests comparing substrings.
+TEST(tokenizeTest, CompareSubstrings) {
+ std::vector<std::string> fields;
+
+ tokenize("find middle one", ' ', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("middle", fields.at(1).c_str());
+ fields.clear();
+
+ // Extra spaces should be ignored.
+ tokenize(" find middle one ", ' ', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("middle", fields.at(1).c_str());
+ fields.clear();
+ tokenize(" ", ' ', &fields);
+ ASSERT_EQ(0ul, fields.size());
+}
+
+TEST(tokenizeTest, TokenizeAppend) {
+ ASSERT_EQ(0ul, tokenize_append("A B C", ' ', NULL));
+
+ std::vector<std::string> fields;
+
+ tokenize_append("A B C", ' ', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("B", fields.at(1).c_str());
+
+ tokenize_append("D E", ' ', &fields);
+ ASSERT_EQ(5ul, fields.size());
+ ASSERT_STREQ("B", fields.at(1).c_str());
+ ASSERT_STREQ("E", fields.at(4).c_str());
+}
+
+TEST(tokenizeTest, TokenizeWithMarks) {
+ ASSERT_EQ(0ul, tokenize("D \"A B", ' ', '(', ')', NULL));
+
+ std::vector<std::string> fields;
+ tokenize("A B C", ' ', '"', '"', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("C", fields.at(2).c_str());
+
+ tokenize("\"A B\" C", ' ', '"', '"', &fields);
+ ASSERT_EQ(2ul, fields.size());
+ ASSERT_STREQ("A B", fields.at(0).c_str());
+
+ tokenize("D \"A B\" C", ' ', '"', '"', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("D", fields.at(0).c_str());
+ ASSERT_STREQ("A B", fields.at(1).c_str());
+
+ tokenize("D \"A B\" C \"E F\"", ' ', '"', '"', &fields);
+ ASSERT_EQ(4ul, fields.size());
+ ASSERT_STREQ("D", fields.at(0).c_str());
+ ASSERT_STREQ("A B", fields.at(1).c_str());
+ ASSERT_STREQ("E F", fields.at(3).c_str());
+
+ // No matching marks.
+ tokenize("D \"A B", ' ', '"', '"', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("D", fields.at(0).c_str());
+ ASSERT_STREQ("\"A", fields.at(1).c_str());
+
+ tokenize("D (A B) C (E F) G", ' ', '(', ')', &fields);
+ ASSERT_EQ(5ul, fields.size());
+ ASSERT_STREQ("D", fields.at(0).c_str());
+ ASSERT_STREQ("A B", fields.at(1).c_str());
+ ASSERT_STREQ("E F", fields.at(3).c_str());
+}
+
+// Tests counting substrings.
+TEST(splitTest, CountSubstrings) {
+ std::vector<std::string> fields;
+
+ EXPECT_EQ(5ul, split("one,two,three,four,five", ',', &fields));
+ fields.clear();
+ EXPECT_EQ(1ul, split("one", ',', &fields));
+
+ // Empty fields between commas count.
+ fields.clear();
+ EXPECT_EQ(5ul, split("one,,three,four,five", ',', &fields));
+ fields.clear();
+ EXPECT_EQ(3ul, split(",three,", ',', &fields));
+ fields.clear();
+ EXPECT_EQ(1ul, split("", ',', &fields));
+}
+
+// Tests comparing substrings.
+TEST(splitTest, CompareSubstrings) {
+ std::vector<std::string> fields;
+
+ split("find,middle,one", ',', &fields);
+ ASSERT_EQ(3ul, fields.size());
+ ASSERT_STREQ("middle", fields.at(1).c_str());
+ fields.clear();
+
+ // Empty fields between commas count.
+ split("find,,middle,one", ',', &fields);
+ ASSERT_EQ(4ul, fields.size());
+ ASSERT_STREQ("middle", fields.at(2).c_str());
+ fields.clear();
+ split("", ',', &fields);
+ ASSERT_EQ(1ul, fields.size());
+ ASSERT_STREQ("", fields.at(0).c_str());
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringutils_unittest.cc b/talk/base/stringutils_unittest.cc
new file mode 100644
index 0000000..5611869
--- /dev/null
+++ b/talk/base/stringutils_unittest.cc
@@ -0,0 +1,126 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/gunit.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+// Tests for string_match().
+
+TEST(string_matchTest, Matches) {
+ EXPECT_TRUE( string_match("A.B.C.D", "a.b.c.d"));
+ EXPECT_TRUE( string_match("www.TEST.GOOGLE.COM", "www.*.com"));
+ EXPECT_TRUE( string_match("127.0.0.1", "12*.0.*1"));
+ EXPECT_TRUE( string_match("127.1.0.21", "12*.0.*1"));
+ EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1"));
+ EXPECT_FALSE(string_match("127.0.0.0", "12*.0.*1"));
+ EXPECT_FALSE(string_match("127.1.1.21", "12*.0.*1"));
+}
+
+// It's not clear if we will ever use wchar_t strings on unix. In theory,
+// all strings should be Utf8 all the time, except when interfacing with Win32
+// APIs that require Utf16.
+
+#ifdef WIN32
+
+// Tests for ascii_string_compare().
+
+// Tests NULL input.
+TEST(ascii_string_compareTest, NullInput) {
+ // The following results in an access violation in
+ // ascii_string_compare. Is this a bug or by design? stringutils.h
+ // should document the expected behavior in this case.
+
+ // EXPECT_EQ(0, ascii_string_compare(NULL, NULL, 1, identity));
+}
+
+// Tests comparing two strings of different lengths.
+TEST(ascii_string_compareTest, DifferentLengths) {
+ EXPECT_EQ(-1, ascii_string_compare(L"Test", "Test1", 5, identity));
+}
+
+// Tests the case where the buffer size is smaller than the string
+// lengths.
+TEST(ascii_string_compareTest, SmallBuffer) {
+ EXPECT_EQ(0, ascii_string_compare(L"Test", "Test1", 3, identity));
+}
+
+// Tests the case where the buffer is not full.
+TEST(ascii_string_compareTest, LargeBuffer) {
+ EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 10, identity));
+}
+
+// Tests comparing two eqaul strings.
+TEST(ascii_string_compareTest, Equal) {
+ EXPECT_EQ(0, ascii_string_compare(L"Test", "Test", 5, identity));
+ EXPECT_EQ(0, ascii_string_compare(L"TeSt", "tEsT", 5, tolowercase));
+}
+
+// Tests comparing a smller string to a larger one.
+TEST(ascii_string_compareTest, LessThan) {
+ EXPECT_EQ(-1, ascii_string_compare(L"abc", "abd", 4, identity));
+ EXPECT_EQ(-1, ascii_string_compare(L"ABC", "abD", 5, tolowercase));
+}
+
+// Tests comparing a larger string to a smaller one.
+TEST(ascii_string_compareTest, GreaterThan) {
+ EXPECT_EQ(1, ascii_string_compare(L"xyz", "xy", 5, identity));
+ EXPECT_EQ(1, ascii_string_compare(L"abc", "ABB", 5, tolowercase));
+}
+#endif // WIN32
+
+TEST(string_trim_Test, Trimming) {
+ EXPECT_EQ("temp", string_trim("\n\r\t temp \n\r\t"));
+ EXPECT_EQ("temp\n\r\t temp", string_trim(" temp\n\r\t temp "));
+ EXPECT_EQ("temp temp", string_trim("temp temp"));
+ EXPECT_EQ("", string_trim(" \r\n\t"));
+ EXPECT_EQ("", string_trim(""));
+}
+
+TEST(string_startsTest, StartsWith) {
+ EXPECT_TRUE(starts_with("foobar", "foo"));
+ EXPECT_TRUE(starts_with("foobar", "foobar"));
+ EXPECT_TRUE(starts_with("foobar", ""));
+ EXPECT_TRUE(starts_with("", ""));
+ EXPECT_FALSE(starts_with("foobar", "bar"));
+ EXPECT_FALSE(starts_with("foobar", "foobarbaz"));
+ EXPECT_FALSE(starts_with("", "f"));
+}
+
+TEST(string_endsTest, EndsWith) {
+ EXPECT_TRUE(ends_with("foobar", "bar"));
+ EXPECT_TRUE(ends_with("foobar", "foobar"));
+ EXPECT_TRUE(ends_with("foobar", ""));
+ EXPECT_TRUE(ends_with("", ""));
+ EXPECT_FALSE(ends_with("foobar", "foo"));
+ EXPECT_FALSE(ends_with("foobar", "foobarbaz"));
+ EXPECT_FALSE(ends_with("", "f"));
+}
+
+} // namespace talk_base
diff --git a/talk/base/task_unittest.cc b/talk/base/task_unittest.cc
new file mode 100644
index 0000000..076ff6f
--- /dev/null
+++ b/talk/base/task_unittest.cc
@@ -0,0 +1,562 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif // POSIX
+
+// TODO: Remove this once the cause of sporadic failures in these
+// tests is tracked down.
+#include <iostream>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/logging.h"
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+static int64 GetCurrentTime() {
+ return static_cast<int64>(Time()) * 10000;
+}
+
+// feel free to change these numbers. Note that '0' won't work, though
+#define STUCK_TASK_COUNT 5
+#define HAPPY_TASK_COUNT 20
+
+// this is a generic timeout task which, when it signals timeout, will
+// include the unique ID of the task in the signal (we don't use this
+// in production code because we haven't yet had occasion to generate
+// an array of the same types of task)
+
+class IdTimeoutTask : public Task, public sigslot::has_slots<> {
+ public:
+ explicit IdTimeoutTask(TaskParent *parent) : Task(parent) {
+ SignalTimeout.connect(this, &IdTimeoutTask::OnLocalTimeout);
+ }
+
+ sigslot::signal1<const int> SignalTimeoutId;
+ sigslot::signal1<const int> SignalDoneId;
+
+ virtual int ProcessStart() {
+ return STATE_RESPONSE;
+ }
+
+ void OnLocalTimeout() {
+ SignalTimeoutId(unique_id());
+ }
+
+ protected:
+ virtual void Stop() {
+ SignalDoneId(unique_id());
+ Task::Stop();
+ }
+};
+
+class StuckTask : public IdTimeoutTask {
+ public:
+ explicit StuckTask(TaskParent *parent) : IdTimeoutTask(parent) {}
+ virtual int ProcessStart() {
+ return STATE_BLOCKED;
+ }
+};
+
+class HappyTask : public IdTimeoutTask {
+ public:
+ explicit HappyTask(TaskParent *parent) : IdTimeoutTask(parent) {
+ time_to_perform_ = rand() % (STUCK_TASK_COUNT / 2);
+ }
+ virtual int ProcessStart() {
+ if (ElapsedTime() > (time_to_perform_ * 1000 * 10000))
+ return STATE_RESPONSE;
+ else
+ return STATE_BLOCKED;
+ }
+
+ private:
+ int time_to_perform_;
+};
+
+// simple implementation of a task runner which uses Windows'
+// GetSystemTimeAsFileTime() to get the current clock ticks
+
+class MyTaskRunner : public TaskRunner {
+ public:
+ virtual void WakeTasks() { RunTasks(); }
+ virtual int64 CurrentTime() {
+ return GetCurrentTime();
+ }
+
+ bool timeout_change() const {
+ return timeout_change_;
+ }
+
+ void clear_timeout_change() {
+ timeout_change_ = false;
+ }
+ protected:
+ virtual void OnTimeoutChange() {
+ timeout_change_ = true;
+ }
+ bool timeout_change_;
+};
+
+//
+// this unit test is primarily concerned (for now) with the timeout
+// functionality in tasks. It works as follows:
+//
+// * Create a bunch of tasks, some "stuck" (ie., guaranteed to timeout)
+// and some "happy" (will immediately finish).
+// * Set the timeout on the "stuck" tasks to some number of seconds between
+// 1 and the number of stuck tasks
+// * Start all the stuck & happy tasks in random order
+// * Wait "number of stuck tasks" seconds and make sure everything timed out
+
+class TaskTest : public sigslot::has_slots<> {
+ public:
+ TaskTest() {}
+
+ // no need to delete any tasks; the task runner owns them
+ ~TaskTest() {}
+
+ void Start() {
+ // create and configure tasks
+ for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+ stuck_[i].task_ = new StuckTask(&task_runner_);
+ stuck_[i].task_->SignalTimeoutId.connect(this,
+ &TaskTest::OnTimeoutStuck);
+ stuck_[i].timed_out_ = false;
+ stuck_[i].xlat_ = stuck_[i].task_->unique_id();
+ stuck_[i].task_->set_timeout_seconds(i + 1);
+ LOG(LS_INFO) << "Task " << stuck_[i].xlat_ << " created with timeout "
+ << stuck_[i].task_->timeout_seconds();
+ }
+
+ for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+ happy_[i].task_ = new HappyTask(&task_runner_);
+ happy_[i].task_->SignalTimeoutId.connect(this,
+ &TaskTest::OnTimeoutHappy);
+ happy_[i].task_->SignalDoneId.connect(this,
+ &TaskTest::OnDoneHappy);
+ happy_[i].timed_out_ = false;
+ happy_[i].xlat_ = happy_[i].task_->unique_id();
+ }
+
+ // start all the tasks in random order
+ int stuck_index = 0;
+ int happy_index = 0;
+ for (int i = 0; i < STUCK_TASK_COUNT + HAPPY_TASK_COUNT; ++i) {
+ if ((stuck_index < STUCK_TASK_COUNT) &&
+ (happy_index < HAPPY_TASK_COUNT)) {
+ if (rand() % 2 == 1) {
+ stuck_[stuck_index++].task_->Start();
+ } else {
+ happy_[happy_index++].task_->Start();
+ }
+ } else if (stuck_index < STUCK_TASK_COUNT) {
+ stuck_[stuck_index++].task_->Start();
+ } else {
+ happy_[happy_index++].task_->Start();
+ }
+ }
+
+ for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+ std::cout << "Stuck task #" << i << " timeout is " <<
+ stuck_[i].task_->timeout_seconds() << " at " <<
+ stuck_[i].task_->timeout_time() << std::endl;
+ }
+
+ // just a little self-check to make sure we started all the tasks
+ ASSERT_EQ(STUCK_TASK_COUNT, stuck_index);
+ ASSERT_EQ(HAPPY_TASK_COUNT, happy_index);
+
+ // run the unblocked tasks
+ LOG(LS_INFO) << "Running tasks";
+ task_runner_.RunTasks();
+
+ std::cout << "Start time is " << GetCurrentTime() << std::endl;
+
+ // give all the stuck tasks time to timeout
+ for (int i = 0; !task_runner_.AllChildrenDone() && i < STUCK_TASK_COUNT;
+ ++i) {
+ Thread::Current()->ProcessMessages(1000);
+ for (int j = 0; j < HAPPY_TASK_COUNT; ++j) {
+ if (happy_[j].task_) {
+ happy_[j].task_->Wake();
+ }
+ }
+ LOG(LS_INFO) << "Polling tasks";
+ task_runner_.PollTasks();
+ }
+
+ // We see occasional test failures here due to the stuck tasks not having
+ // timed-out yet, which seems like it should be impossible. To help track
+ // this down we have added logging of the timing information, which we send
+ // directly to stdout so that we get it in opt builds too.
+ std::cout << "End time is " << GetCurrentTime() << std::endl;
+ }
+
+ void OnTimeoutStuck(const int id) {
+ LOG(LS_INFO) << "Timed out task " << id;
+
+ int i;
+ for (i = 0; i < STUCK_TASK_COUNT; ++i) {
+ if (stuck_[i].xlat_ == id) {
+ stuck_[i].timed_out_ = true;
+ stuck_[i].task_ = NULL;
+ break;
+ }
+ }
+
+ // getting a bad ID here is a failure, but let's continue
+ // running to see what else might go wrong
+ EXPECT_LT(i, STUCK_TASK_COUNT);
+ }
+
+ void OnTimeoutHappy(const int id) {
+ int i;
+ for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+ if (happy_[i].xlat_ == id) {
+ happy_[i].timed_out_ = true;
+ happy_[i].task_ = NULL;
+ break;
+ }
+ }
+
+ // getting a bad ID here is a failure, but let's continue
+ // running to see what else might go wrong
+ EXPECT_LT(i, HAPPY_TASK_COUNT);
+ }
+
+ void OnDoneHappy(const int id) {
+ int i;
+ for (i = 0; i < HAPPY_TASK_COUNT; ++i) {
+ if (happy_[i].xlat_ == id) {
+ happy_[i].task_ = NULL;
+ break;
+ }
+ }
+
+ // getting a bad ID here is a failure, but let's continue
+ // running to see what else might go wrong
+ EXPECT_LT(i, HAPPY_TASK_COUNT);
+ }
+
+ void check_passed() {
+ EXPECT_TRUE(task_runner_.AllChildrenDone());
+
+ // make sure none of our happy tasks timed out
+ for (int i = 0; i < HAPPY_TASK_COUNT; ++i) {
+ EXPECT_FALSE(happy_[i].timed_out_);
+ }
+
+ // make sure all of our stuck tasks timed out
+ for (int i = 0; i < STUCK_TASK_COUNT; ++i) {
+ EXPECT_TRUE(stuck_[i].timed_out_);
+ if (!stuck_[i].timed_out_) {
+ std::cout << "Stuck task #" << i << " timeout is at "
+ << stuck_[i].task_->timeout_time() << std::endl;
+ }
+ }
+
+ std::cout.flush();
+ }
+
+ private:
+ struct TaskInfo {
+ IdTimeoutTask *task_;
+ bool timed_out_;
+ int xlat_;
+ };
+
+ MyTaskRunner task_runner_;
+ TaskInfo stuck_[STUCK_TASK_COUNT];
+ TaskInfo happy_[HAPPY_TASK_COUNT];
+};
+
+TEST(start_task_test, Timeout) {
+ TaskTest task_test;
+ task_test.Start();
+ task_test.check_passed();
+}
+
+// Test for aborting the task while it is running
+
+class AbortTask : public Task {
+ public:
+ explicit AbortTask(TaskParent *parent) : Task(parent) {
+ set_timeout_seconds(1);
+ }
+
+ virtual int ProcessStart() {
+ Abort();
+ return STATE_NEXT;
+ }
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(AbortTask);
+};
+
+class TaskAbortTest : public sigslot::has_slots<> {
+ public:
+ TaskAbortTest() {}
+
+ // no need to delete any tasks; the task runner owns them
+ ~TaskAbortTest() {}
+
+ void Start() {
+ Task *abort_task = new AbortTask(&task_runner_);
+ abort_task->SignalTimeout.connect(this, &TaskAbortTest::OnTimeout);
+ abort_task->Start();
+
+ // run the task
+ task_runner_.RunTasks();
+ }
+
+ private:
+ void OnTimeout() {
+ FAIL() << "Task timed out instead of aborting.";
+ }
+
+ MyTaskRunner task_runner_;
+ DISALLOW_EVIL_CONSTRUCTORS(TaskAbortTest);
+};
+
+TEST(start_task_test, Abort) {
+ TaskAbortTest abort_test;
+ abort_test.Start();
+}
+
+// Test for aborting a task to verify that it does the Wake operation
+// which gets it deleted.
+
+class SetBoolOnDeleteTask : public Task {
+ public:
+ SetBoolOnDeleteTask(TaskParent *parent, bool *set_when_deleted)
+ : Task(parent),
+ set_when_deleted_(set_when_deleted) {
+ EXPECT_TRUE(NULL != set_when_deleted);
+ EXPECT_FALSE(*set_when_deleted);
+ }
+
+ virtual ~SetBoolOnDeleteTask() {
+ *set_when_deleted_ = true;
+ }
+
+ virtual int ProcessStart() {
+ return STATE_BLOCKED;
+ }
+
+ private:
+ bool* set_when_deleted_;
+ DISALLOW_EVIL_CONSTRUCTORS(SetBoolOnDeleteTask);
+};
+
+class AbortShouldWakeTest : public sigslot::has_slots<> {
+ public:
+ AbortShouldWakeTest() {}
+
+ // no need to delete any tasks; the task runner owns them
+ ~AbortShouldWakeTest() {}
+
+ void Start() {
+ bool task_deleted = false;
+ Task *task_to_abort = new SetBoolOnDeleteTask(&task_runner_, &task_deleted);
+ task_to_abort->Start();
+
+ // Task::Abort() should call TaskRunner::WakeTasks(). WakeTasks calls
+ // TaskRunner::RunTasks() immediately which should delete the task.
+ task_to_abort->Abort();
+ EXPECT_TRUE(task_deleted);
+
+ if (!task_deleted) {
+ // avoid a crash (due to referencing a local variable)
+ // if the test fails.
+ task_runner_.RunTasks();
+ }
+ }
+
+ private:
+ void OnTimeout() {
+ FAIL() << "Task timed out instead of aborting.";
+ }
+
+ MyTaskRunner task_runner_;
+ DISALLOW_EVIL_CONSTRUCTORS(AbortShouldWakeTest);
+};
+
+TEST(start_task_test, AbortShouldWake) {
+ AbortShouldWakeTest abort_should_wake_test;
+ abort_should_wake_test.Start();
+}
+
+// Validate that TaskRunner's OnTimeoutChange gets called appropriately
+// * When a task calls UpdateTaskTimeout
+// * When the next timeout task time, times out
+class TimeoutChangeTest : public sigslot::has_slots<> {
+ public:
+ TimeoutChangeTest()
+ : task_count_(ARRAY_SIZE(stuck_tasks_)) {}
+
+ // no need to delete any tasks; the task runner owns them
+ ~TimeoutChangeTest() {}
+
+ void Start() {
+ for (int i = 0; i < task_count_; ++i) {
+ stuck_tasks_[i] = new StuckTask(&task_runner_);
+ stuck_tasks_[i]->set_timeout_seconds(i + 2);
+ stuck_tasks_[i]->SignalTimeoutId.connect(this,
+ &TimeoutChangeTest::OnTimeoutId);
+ }
+
+ for (int i = task_count_ - 1; i >= 0; --i) {
+ stuck_tasks_[i]->Start();
+ }
+ task_runner_.clear_timeout_change();
+
+ // At this point, our timeouts are set as follows
+ // task[0] is 2 seconds, task[1] at 3 seconds, etc.
+
+ stuck_tasks_[0]->set_timeout_seconds(2);
+ // Now, task[0] is 2 seconds, task[1] at 3 seconds...
+ // so timeout change shouldn't be called.
+ EXPECT_FALSE(task_runner_.timeout_change());
+ task_runner_.clear_timeout_change();
+
+ stuck_tasks_[0]->set_timeout_seconds(1);
+ // task[0] is 1 seconds, task[1] at 3 seconds...
+ // The smallest timeout got smaller so timeout change be called.
+ EXPECT_TRUE(task_runner_.timeout_change());
+ task_runner_.clear_timeout_change();
+
+ stuck_tasks_[1]->set_timeout_seconds(2);
+ // task[0] is 1 seconds, task[1] at 2 seconds...
+ // The smallest timeout is still 1 second so no timeout change.
+ EXPECT_FALSE(task_runner_.timeout_change());
+ task_runner_.clear_timeout_change();
+
+ while (task_count_ > 0) {
+ int previous_count = task_count_;
+ task_runner_.PollTasks();
+ if (previous_count != task_count_) {
+ // We only get here when a task times out. When that
+ // happens, the timeout change should get called because
+ // the smallest timeout is now in the past.
+ EXPECT_TRUE(task_runner_.timeout_change());
+ task_runner_.clear_timeout_change();
+ }
+ Thread::Current()->socketserver()->Wait(500, false);
+ }
+ }
+
+ private:
+ void OnTimeoutId(const int id) {
+ for (int i = 0; i < ARRAY_SIZE(stuck_tasks_); ++i) {
+ if (stuck_tasks_[i] && stuck_tasks_[i]->unique_id() == id) {
+ task_count_--;
+ stuck_tasks_[i] = NULL;
+ break;
+ }
+ }
+ }
+
+ MyTaskRunner task_runner_;
+ StuckTask* (stuck_tasks_[3]);
+ int task_count_;
+ DISALLOW_EVIL_CONSTRUCTORS(TimeoutChangeTest);
+};
+
+TEST(start_task_test, TimeoutChange) {
+ TimeoutChangeTest timeout_change_test;
+ timeout_change_test.Start();
+}
+
+class DeleteTestTaskRunner : public TaskRunner {
+ public:
+ DeleteTestTaskRunner() {
+ }
+ virtual void WakeTasks() { }
+ virtual int64 CurrentTime() {
+ return GetCurrentTime();
+ }
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(DeleteTestTaskRunner);
+};
+
+TEST(unstarted_task_test, DeleteTask) {
+ // This test ensures that we don't
+ // crash if a task is deleted without running it.
+ DeleteTestTaskRunner task_runner;
+ HappyTask* happy_task = new HappyTask(&task_runner);
+ happy_task->Start();
+
+ // try deleting the task directly
+ HappyTask* child_happy_task = new HappyTask(happy_task);
+ delete child_happy_task;
+
+ // run the unblocked tasks
+ task_runner.RunTasks();
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask1) {
+ // This test ensures that we don't
+ // crash if a task runner is deleted without
+ // running a certain task.
+ DeleteTestTaskRunner task_runner;
+ HappyTask* happy_task = new HappyTask(&task_runner);
+ happy_task->Start();
+
+ HappyTask* child_happy_task = new HappyTask(happy_task);
+ child_happy_task->Start();
+
+ // Never run the tasks
+}
+
+TEST(unstarted_task_test, DoNotDeleteTask2) {
+ // This test ensures that we don't
+ // crash if a taskrunner is delete with a
+ // task that has never been started.
+ DeleteTestTaskRunner task_runner;
+ HappyTask* happy_task = new HappyTask(&task_runner);
+ happy_task->Start();
+
+ // Do not start the task.
+ // Note: this leaks memory, so don't do this.
+ // Instead, always run your tasks or delete them.
+ new HappyTask(happy_task);
+
+ // run the unblocked tasks
+ task_runner.RunTasks();
+}
+
+} // namespace talk_base
diff --git a/talk/base/testechoserver.h b/talk/base/testechoserver.h
new file mode 100644
index 0000000..0b645d1
--- /dev/null
+++ b/talk/base/testechoserver.h
@@ -0,0 +1,63 @@
+// Copyright 2008 Google Inc. All Rights Reserved.
+
+
+#ifndef TALK_BASE_TESTECHOSERVER_H_
+#define TALK_BASE_TESTECHOSERVER_H_
+
+#include <list>
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+// A test echo server, echoes back any packets sent to it.
+// Useful for unit tests.
+class TestEchoServer : public sigslot::has_slots<> {
+ public:
+ TestEchoServer(Thread* thread, const SocketAddress& addr)
+ : server_socket_(thread->socketserver()->CreateAsyncSocket(SOCK_STREAM)) {
+ server_socket_->Bind(addr);
+ server_socket_->Listen(5);
+ server_socket_->SignalReadEvent.connect(this, &TestEchoServer::OnAccept);
+ }
+ ~TestEchoServer() {
+ for (ClientList::iterator it = client_sockets_.begin();
+ it != client_sockets_.end(); ++it) {
+ delete *it;
+ }
+ }
+
+ SocketAddress address() const { return server_socket_->GetLocalAddress(); }
+
+ private:
+ void OnAccept(AsyncSocket* socket) {
+ AsyncSocket* raw_socket = socket->Accept(NULL);
+ if (raw_socket) {
+ AsyncTCPSocket* packet_socket = new AsyncTCPSocket(raw_socket, false);
+ packet_socket->SignalReadPacket.connect(this, &TestEchoServer::OnPacket);
+ packet_socket->SignalClose.connect(this, &TestEchoServer::OnClose);
+ client_sockets_.push_back(packet_socket);
+ }
+ }
+ void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+ const SocketAddress& remote_addr) {
+ socket->Send(buf, size);
+ }
+ void OnClose(AsyncPacketSocket* socket, int err) {
+ ClientList::iterator it =
+ std::find(client_sockets_.begin(), client_sockets_.end(), socket);
+ client_sockets_.erase(it);
+ Thread::Current()->Dispose(socket);
+ }
+
+ typedef std::list<AsyncTCPSocket*> ClientList;
+ scoped_ptr<AsyncSocket> server_socket_;
+ ClientList client_sockets_;
+ DISALLOW_EVIL_CONSTRUCTORS(TestEchoServer);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TESTECHOSERVER_H_
diff --git a/talk/base/testutils.h b/talk/base/testutils.h
new file mode 100644
index 0000000..4dc648e
--- /dev/null
+++ b/talk/base/testutils.h
@@ -0,0 +1,568 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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.
+ */
+
+#ifndef TALK_BASE_TESTUTILS_H__
+#define TALK_BASE_TESTUTILS_H__
+
+// Utilities for testing talk_base infrastructure in unittests
+
+#include <map>
+#include <vector>
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace testing {
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSink - Monitor asynchronously signalled events from StreamInterface
+// or AsyncSocket (which should probably be a StreamInterface.
+///////////////////////////////////////////////////////////////////////////////
+
+// Note: Any event that is an error is treaded as SSE_ERROR instead of that
+// event.
+
+enum StreamSinkEvent {
+ SSE_OPEN = SE_OPEN,
+ SSE_READ = SE_READ,
+ SSE_WRITE = SE_WRITE,
+ SSE_CLOSE = SE_CLOSE,
+ SSE_ERROR = 16
+};
+
+class StreamSink : public sigslot::has_slots<> {
+ public:
+ void Monitor(StreamInterface* stream) {
+ stream->SignalEvent.connect(this, &StreamSink::OnEvent);
+ events_.erase(stream);
+ }
+ void Unmonitor(StreamInterface* stream) {
+ stream->SignalEvent.disconnect(this);
+ // In case you forgot to unmonitor a previous object with this address
+ events_.erase(stream);
+ }
+ bool Check(StreamInterface* stream, StreamSinkEvent event, bool reset = true) {
+ return DoCheck(stream, event, reset);
+ }
+ int Events(StreamInterface* stream, bool reset = true) {
+ return DoEvents(stream, reset);
+ }
+
+ void Monitor(AsyncSocket* socket) {
+ socket->SignalConnectEvent.connect(this, &StreamSink::OnConnectEvent);
+ socket->SignalReadEvent.connect(this, &StreamSink::OnReadEvent);
+ socket->SignalWriteEvent.connect(this, &StreamSink::OnWriteEvent);
+ socket->SignalCloseEvent.connect(this, &StreamSink::OnCloseEvent);
+ // In case you forgot to unmonitor a previous object with this address
+ events_.erase(socket);
+ }
+ void Unmonitor(AsyncSocket* socket) {
+ socket->SignalConnectEvent.disconnect(this);
+ socket->SignalReadEvent.disconnect(this);
+ socket->SignalWriteEvent.disconnect(this);
+ socket->SignalCloseEvent.disconnect(this);
+ events_.erase(socket);
+ }
+ bool Check(AsyncSocket* socket, StreamSinkEvent event, bool reset = true) {
+ return DoCheck(socket, event, reset);
+ }
+ int Events(AsyncSocket* socket, bool reset = true) {
+ return DoEvents(socket, reset);
+ }
+
+ private:
+ typedef std::map<void*,int> EventMap;
+
+ void OnEvent(StreamInterface* stream, int events, int error) {
+ if (error) {
+ events = SSE_ERROR;
+ }
+ AddEvents(stream, events);
+ }
+ void OnConnectEvent(AsyncSocket* socket) {
+ AddEvents(socket, SSE_OPEN);
+ }
+ void OnReadEvent(AsyncSocket* socket) {
+ AddEvents(socket, SSE_READ);
+ }
+ void OnWriteEvent(AsyncSocket* socket) {
+ AddEvents(socket, SSE_WRITE);
+ }
+ void OnCloseEvent(AsyncSocket* socket, int error) {
+ AddEvents(socket, (0 == error) ? SSE_CLOSE : SSE_ERROR);
+ }
+
+ void AddEvents(void* obj, int events) {
+ EventMap::iterator it = events_.find(obj);
+ if (events_.end() == it) {
+ events_.insert(EventMap::value_type(obj, events));
+ } else {
+ it->second |= events;
+ }
+ }
+ bool DoCheck(void* obj, StreamSinkEvent event, bool reset) {
+ EventMap::iterator it = events_.find(obj);
+ if ((events_.end() == it) || (0 == (it->second & event))) {
+ return false;
+ }
+ if (reset) {
+ it->second &= ~event;
+ }
+ return true;
+ }
+ int DoEvents(void* obj, bool reset) {
+ EventMap::iterator it = events_.find(obj);
+ if (events_.end() == it)
+ return 0;
+ int events = it->second;
+ if (reset) {
+ it->second = 0;
+ }
+ return events;
+ }
+
+ EventMap events_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSource - Implements stream interface and simulates asynchronous
+// events on the stream, without a network. Also buffers written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSource : public StreamInterface {
+public:
+ StreamSource() {
+ Clear();
+ }
+
+ void Clear() {
+ readable_data_.clear();
+ written_data_.clear();
+ state_ = SS_CLOSED;
+ read_block_ = 0;
+ write_block_ = SIZE_UNKNOWN;
+ }
+ void QueueString(const char* data) {
+ QueueData(data, strlen(data));
+ }
+ void QueueStringF(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buffer[1024];
+ size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+ ASSERT(len < sizeof(buffer) - 1);
+ va_end(args);
+ QueueData(buffer, len);
+ }
+ void QueueData(const char* data, size_t len) {
+ readable_data_.insert(readable_data_.end(), data, data + len);
+ if ((SS_OPEN == state_) && (readable_data_.size() == len)) {
+ SignalEvent(this, SE_READ, 0);
+ }
+ }
+ std::string ReadData() {
+ std::string data;
+ // avoid accessing written_data_[0] if it is undefined
+ if (written_data_.size() > 0) {
+ data.insert(0, &written_data_[0], written_data_.size());
+ }
+ written_data_.clear();
+ return data;
+ }
+ void SetState(StreamState state) {
+ int events = 0;
+ if ((SS_OPENING == state_) && (SS_OPEN == state)) {
+ events |= SE_OPEN;
+ if (!readable_data_.empty()) {
+ events |= SE_READ;
+ }
+ } else if ((SS_CLOSED != state_) && (SS_CLOSED == state)) {
+ events |= SE_CLOSE;
+ }
+ state_ = state;
+ if (events) {
+ SignalEvent(this, events, 0);
+ }
+ }
+ // Will cause Read to block when there are pos bytes in the read queue.
+ void SetReadBlock(size_t pos) { read_block_ = pos; }
+ // Will cause Write to block when there are pos bytes in the write queue.
+ void SetWriteBlock(size_t pos) { write_block_ = pos; }
+
+ virtual StreamState GetState() const { return state_; }
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (SS_CLOSED == state_) {
+ if (error) *error = -1;
+ return SR_ERROR;
+ }
+ if ((SS_OPENING == state_) || (readable_data_.size() <= read_block_)) {
+ return SR_BLOCK;
+ }
+ size_t count = _min(buffer_len, readable_data_.size() - read_block_);
+ memcpy(buffer, &readable_data_[0], count);
+ size_t new_size = readable_data_.size() - count;
+ // Avoid undefined access beyond the last element of the vector.
+ // This only happens when new_size is 0.
+ if (count < readable_data_.size()) {
+ memmove(&readable_data_[0], &readable_data_[count], new_size);
+ }
+ readable_data_.resize(new_size);
+ if (read) *read = count;
+ return SR_SUCCESS;
+ }
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (SS_CLOSED == state_) {
+ if (error) *error = -1;
+ return SR_ERROR;
+ }
+ if (SS_OPENING == state_) {
+ return SR_BLOCK;
+ }
+ if (SIZE_UNKNOWN != write_block_) {
+ if (written_data_.size() >= write_block_) {
+ return SR_BLOCK;
+ }
+ if (data_len > (write_block_ - written_data_.size())) {
+ data_len = write_block_ - written_data_.size();
+ }
+ }
+ if (written) *written = data_len;
+ const char* cdata = static_cast<const char*>(data);
+ written_data_.insert(written_data_.end(), cdata, cdata + data_len);
+ return SR_SUCCESS;
+ }
+ virtual void Close() { state_ = SS_CLOSED; }
+
+private:
+ typedef std::vector<char> Buffer;
+ Buffer readable_data_, written_data_;
+ StreamState state_;
+ size_t read_block_, write_block_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestClient
+// Creates a simulated client for testing. Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestClient : public sigslot::has_slots<> {
+public:
+ SocketTestClient() {
+ Init(NULL);
+ }
+ SocketTestClient(AsyncSocket* socket) {
+ Init(socket);
+ }
+ SocketTestClient(const SocketAddress& address) {
+ Init(NULL);
+ socket_->Connect(address);
+ }
+
+ AsyncSocket* socket() { return socket_.get(); }
+
+ void QueueString(const char* data) {
+ QueueData(data, strlen(data));
+ }
+ void QueueStringF(const char* format, ...) {
+ va_list args;
+ va_start(args, format);
+ char buffer[1024];
+ size_t len = vsprintfn(buffer, sizeof(buffer), format, args);
+ ASSERT(len < sizeof(buffer) - 1);
+ va_end(args);
+ QueueData(buffer, len);
+ }
+ void QueueData(const char* data, size_t len) {
+ send_buffer_.insert(send_buffer_.end(), data, data + len);
+ if (Socket::CS_CONNECTED == socket_->GetState()) {
+ Flush();
+ }
+ }
+ std::string ReadData() {
+ std::string data(&recv_buffer_[0], recv_buffer_.size());
+ recv_buffer_.clear();
+ return data;
+ }
+
+ bool IsConnected() const {
+ return (Socket::CS_CONNECTED == socket_->GetState());
+ }
+ bool IsClosed() const {
+ return (Socket::CS_CLOSED == socket_->GetState());
+ }
+
+private:
+ typedef std::vector<char> Buffer;
+
+ void Init(AsyncSocket* socket) {
+ if (!socket) {
+ socket = Thread::Current()->socketserver()
+ ->CreateAsyncSocket(SOCK_STREAM);
+ }
+ socket_.reset(socket);
+ socket_->SignalConnectEvent.connect(this,
+ &SocketTestClient::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this, &SocketTestClient::OnReadEvent);
+ socket_->SignalWriteEvent.connect(this, &SocketTestClient::OnWriteEvent);
+ socket_->SignalCloseEvent.connect(this, &SocketTestClient::OnCloseEvent);
+ }
+
+ void Flush() {
+ size_t sent = 0;
+ while (sent < send_buffer_.size()) {
+ int result = socket_->Send(&send_buffer_[sent],
+ send_buffer_.size() - sent);
+ if (result > 0) {
+ sent += result;
+ } else {
+ break;
+ }
+ }
+ size_t new_size = send_buffer_.size() - sent;
+ memmove(&send_buffer_[0], &send_buffer_[sent], new_size);
+ send_buffer_.resize(new_size);
+ }
+
+ void OnConnectEvent(AsyncSocket* socket) {
+ if (!send_buffer_.empty()) {
+ Flush();
+ }
+ }
+ void OnReadEvent(AsyncSocket* socket) {
+ char data[64 * 1024];
+ int result = socket_->Recv(data, ARRAY_SIZE(data));
+ if (result > 0) {
+ recv_buffer_.insert(recv_buffer_.end(), data, data + result);
+ }
+ }
+ void OnWriteEvent(AsyncSocket* socket) {
+ if (!send_buffer_.empty()) {
+ Flush();
+ }
+ }
+ void OnCloseEvent(AsyncSocket* socket, int error) {
+ }
+
+ scoped_ptr<AsyncSocket> socket_;
+ Buffer send_buffer_, recv_buffer_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SocketTestServer
+// Creates a simulated server for testing. Works on real and virtual networks.
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketTestServer : public sigslot::has_slots<> {
+ public:
+ SocketTestServer(const SocketAddress& address)
+ : socket_(Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM))
+ {
+ socket_->SignalReadEvent.connect(this, &SocketTestServer::OnReadEvent);
+ socket_->Bind(address);
+ socket_->Listen(5);
+ }
+ virtual ~SocketTestServer() {
+ clear();
+ }
+
+ size_t size() const { return clients_.size(); }
+ SocketTestClient* client(size_t index) const { return clients_[index]; }
+ SocketTestClient* operator[](size_t index) const { return client(index); }
+
+ void clear() {
+ for (size_t i=0; i<clients_.size(); ++i) {
+ delete clients_[i];
+ }
+ clients_.clear();
+ }
+
+ private:
+ void OnReadEvent(AsyncSocket* socket) {
+ AsyncSocket* accepted =
+ static_cast<AsyncSocket*>(socket_->Accept(NULL));
+ if (!accepted)
+ return;
+ clients_.push_back(new SocketTestClient(accepted));
+ }
+
+ scoped_ptr<AsyncSocket> socket_;
+ std::vector<SocketTestClient*> clients_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic Utilities
+///////////////////////////////////////////////////////////////////////////////
+
+inline bool ReadFile(const char* filename, std::string* contents) {
+ FILE* fp = fopen(filename, "rb");
+ if (!fp)
+ return false;
+ char buffer[1024*64];
+ size_t read;
+ contents->clear();
+ while ((read = fread(buffer, 1, sizeof(buffer), fp))) {
+ contents->append(buffer, read);
+ }
+ bool success = (0 != feof(fp));
+ fclose(fp);
+ return success;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Unittest predicates which are similar to STREQ, but for raw memory
+///////////////////////////////////////////////////////////////////////////////
+
+inline AssertionResult CmpHelperMemEq(const char* expected_expression,
+ const char* expected_length_expression,
+ const char* actual_expression,
+ const char* actual_length_expression,
+ const void* expected,
+ size_t expected_length,
+ const void* actual,
+ size_t actual_length)
+{
+ if ((expected_length == actual_length)
+ && (0 == memcmp(expected, actual, expected_length))) {
+ return AssertionSuccess();
+ }
+
+ Message msg;
+ msg << "Value of: " << actual_expression
+ << " [" << actual_length_expression << "]";
+ if (true) { //!actual_value.Equals(actual_expression)) {
+ size_t buffer_size = actual_length * 2 + 1;
+ char* buffer = STACK_ARRAY(char, buffer_size);
+ hex_encode(buffer, buffer_size,
+ reinterpret_cast<const char*>(actual), actual_length);
+ msg << "\n Actual: " << buffer << " [" << actual_length << "]";
+ }
+
+ msg << "\nExpected: " << expected_expression
+ << " [" << expected_length_expression << "]";
+ if (true) { //!expected_value.Equals(expected_expression)) {
+ size_t buffer_size = expected_length * 2 + 1;
+ char* buffer = STACK_ARRAY(char, buffer_size);
+ hex_encode(buffer, buffer_size,
+ reinterpret_cast<const char*>(expected), expected_length);
+ msg << "\nWhich is: " << buffer << " [" << expected_length << "]";
+ }
+
+ return AssertionFailure(msg);
+}
+
+inline AssertionResult CmpHelperFileEq(const char* expected_expression,
+ const char* expected_length_expression,
+ const char* actual_filename,
+ const void* expected,
+ size_t expected_length,
+ const char* filename)
+{
+ std::string contents;
+ if (!ReadFile(filename, &contents)) {
+ Message msg;
+ msg << "File '" << filename << "' could not be read.";
+ return AssertionFailure(msg);
+ }
+ return CmpHelperMemEq(expected_expression, expected_length_expression,
+ actual_filename, "",
+ expected, expected_length,
+ contents.c_str(), contents.size());
+}
+
+#define EXPECT_MEMEQ(expected, expected_length, actual, actual_length) \
+ EXPECT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+ actual, actual_length)
+
+#define ASSERT_MEMEQ(expected, expected_length, actual, actual_length) \
+ ASSERT_PRED_FORMAT4(::testing::CmpHelperMemEq, expected, expected_length, \
+ actual, actual_length)
+
+#define EXPECT_FILEEQ(expected, expected_length, filename) \
+ EXPECT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+ filename)
+
+#define ASSERT_FILEEQ(expected, expected_length, filename) \
+ ASSERT_PRED_FORMAT3(::testing::CmpHelperFileEq, expected, expected_length, \
+ filename)
+
+///////////////////////////////////////////////////////////////////////////////
+// Helpers for initializing constant memory with integers in a particular byte
+// order
+///////////////////////////////////////////////////////////////////////////////
+
+#define BYTE_CAST(x) static_cast<uint8>((x) & 0xFF)
+
+// Declare a N-bit integer as a little-endian sequence of bytes
+#define LE16(x) BYTE_CAST(((uint16)x) >> 0), BYTE_CAST(((uint16)x) >> 8)
+
+#define LE32(x) BYTE_CAST(((uint32)x) >> 0), BYTE_CAST(((uint32)x) >> 8), \
+ BYTE_CAST(((uint32)x) >> 16), BYTE_CAST(((uint32)x) >> 24)
+
+#define LE64(x) BYTE_CAST(((uint64)x) >> 0), BYTE_CAST(((uint64)x) >> 8), \
+ BYTE_CAST(((uint64)x) >> 16), BYTE_CAST(((uint64)x) >> 24), \
+ BYTE_CAST(((uint64)x) >> 32), BYTE_CAST(((uint64)x) >> 40), \
+ BYTE_CAST(((uint64)x) >> 48), BYTE_CAST(((uint64)x) >> 56)
+
+// Declare a N-bit integer as a big-endian (Internet) sequence of bytes
+#define BE16(x) BYTE_CAST(((uint16)x) >> 8), BYTE_CAST(((uint16)x) >> 0)
+
+#define BE32(x) BYTE_CAST(((uint32)x) >> 24), BYTE_CAST(((uint32)x) >> 16), \
+ BYTE_CAST(((uint32)x) >> 8), BYTE_CAST(((uint32)x) >> 0)
+
+#define BE64(x) BYTE_CAST(((uint64)x) >> 56), BYTE_CAST(((uint64)x) >> 48), \
+ BYTE_CAST(((uint64)x) >> 40), BYTE_CAST(((uint64)x) >> 32), \
+ BYTE_CAST(((uint64)x) >> 24), BYTE_CAST(((uint64)x) >> 16), \
+ BYTE_CAST(((uint64)x) >> 8), BYTE_CAST(((uint64)x) >> 0)
+
+// Declare a N-bit integer as a this-endian (local machine) sequence of bytes
+#ifndef BIG_ENDIAN
+#define BIG_ENDIAN 1
+#endif // BIG_ENDIAN
+
+#if BIG_ENDIAN
+#define TE16 BE16
+#define TE32 BE32
+#define TE64 BE64
+#else // !BIG_ENDIAN
+#define TE16 LE16
+#define TE32 LE32
+#define TE64 LE64
+#endif // !BIG_ENDIAN
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace testing
+
+#endif // TALK_BASE_TESTUTILS_H__
diff --git a/talk/base/thread_unittest.cc b/talk/base/thread_unittest.cc
new file mode 100644
index 0000000..a70b52d
--- /dev/null
+++ b/talk/base/thread_unittest.cc
@@ -0,0 +1,260 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/asyncudpsocket.h"
+#include "talk/base/gunit.h"
+#include "talk/base/host.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+
+#ifdef WIN32
+#include <comdef.h> // NOLINT
+#endif
+
+using namespace talk_base;
+
+const int MAX = 65536;
+
+// Generates a sequence of numbers (collaboratively).
+class TestGenerator {
+ public:
+ TestGenerator() : last(0), count(0) {}
+
+ int Next(int prev) {
+ int result = prev + last;
+ last = result;
+ count += 1;
+ return result;
+ }
+
+ int last;
+ int count;
+};
+
+struct TestMessage : public MessageData {
+ explicit TestMessage(int v) : value(v) {}
+ virtual ~TestMessage() {}
+
+ int value;
+};
+
+// Receives on a socket and sends by posting messages.
+class SocketClient : public TestGenerator, public sigslot::has_slots<> {
+ public:
+ SocketClient(AsyncSocket* socket, const SocketAddress& addr,
+ Thread* post_thread, MessageHandler* phandler)
+ : socket_(AsyncUDPSocket::Create(socket, addr)),
+ post_thread_(post_thread),
+ post_handler_(phandler) {
+ socket_->SignalReadPacket.connect(this, &SocketClient::OnPacket);
+ }
+
+ ~SocketClient() {
+ delete socket_;
+ }
+
+ SocketAddress address() const { return socket_->GetLocalAddress(); }
+
+ void OnPacket(AsyncPacketSocket* socket, const char* buf, size_t size,
+ const SocketAddress& remote_addr) {
+ EXPECT_EQ(size, sizeof(uint32));
+ uint32 prev = reinterpret_cast<const uint32*>(buf)[0];
+ uint32 result = Next(prev);
+
+ //socket_->set_readable(last < MAX);
+ post_thread_->PostDelayed(200, post_handler_, 0, new TestMessage(result));
+ }
+
+ private:
+ AsyncUDPSocket* socket_;
+ Thread* post_thread_;
+ MessageHandler* post_handler_;
+};
+
+// Receives messages and sends on a socket.
+class MessageClient : public MessageHandler, public TestGenerator {
+ public:
+ MessageClient(Thread* pth, Socket* socket)
+ : thread_(pth), socket_(socket) {
+ }
+
+ virtual ~MessageClient() {
+ delete socket_;
+ }
+
+ virtual void OnMessage(Message *pmsg) {
+ TestMessage* msg = static_cast<TestMessage*>(pmsg->pdata);
+ int result = Next(msg->value);
+ EXPECT_GE(socket_->Send(&result, sizeof(result)), 0);
+ delete msg;
+ }
+
+ private:
+ Thread* thread_;
+ Socket* socket_;
+};
+
+class CustomThread : public talk_base::Thread {
+ public:
+ CustomThread() {}
+ virtual ~CustomThread() {}
+ bool Start() { return false; }
+
+ bool WrapCurrent() { return talk_base::Thread::WrapCurrent(); }
+ void UnwrapCurrent() { talk_base::Thread::UnwrapCurrent(); }
+};
+
+
+TEST(ThreadTest, Main) {
+ const SocketAddress addr("127.0.0.1", 0);
+
+ // Create the messaging client on its own thread.
+ Thread th1;
+ Socket* socket = th1.socketserver()->CreateSocket(SOCK_DGRAM);
+ MessageClient msg_client(&th1, socket);
+
+ // Create the socket client on its own thread.
+ Thread th2;
+ AsyncSocket* asocket = th2.socketserver()->CreateAsyncSocket(SOCK_DGRAM);
+ SocketClient sock_client(asocket, addr, &th1, &msg_client);
+
+ socket->Connect(sock_client.address());
+
+ th1.Start();
+ th2.Start();
+
+ // Get the messages started.
+ th1.PostDelayed(100, &msg_client, 0, new TestMessage(1));
+
+ // Give the clients a little while to run.
+ // Messages will be processed at 100, 300, 500, 700, 900.
+ Thread* th_main = Thread::Current();
+ th_main->ProcessMessages(1000);
+
+ // Stop the sending client. Give the receiver a bit longer to run, in case
+ // it is running on a machine that is under load (e.g. the build machine).
+ th1.Stop();
+ th_main->ProcessMessages(200);
+ th2.Stop();
+
+ // Make sure the results were correct
+ EXPECT_EQ(5, msg_client.count);
+ EXPECT_EQ(34, msg_client.last);
+ EXPECT_EQ(5, sock_client.count);
+ EXPECT_EQ(55, sock_client.last);
+}
+
+// Test that setting thread names doesn't cause a malfunction.
+// There's no easy way to verify the name was set properly at this time.
+TEST(ThreadTest, Names) {
+ // Default name
+ Thread *thread;
+ thread = new Thread();
+ EXPECT_TRUE(thread->Start());
+ thread->Stop();
+ delete thread;
+ thread = new Thread();
+ // Name with no object parameter
+ EXPECT_TRUE(thread->SetName("No object", NULL));
+ EXPECT_TRUE(thread->Start());
+ thread->Stop();
+ delete thread;
+ // Really long name
+ thread = new Thread();
+ EXPECT_TRUE(thread->SetName("Abcdefghijklmnopqrstuvwxyz1234567890", this));
+ EXPECT_TRUE(thread->Start());
+ thread->Stop();
+ delete thread;
+}
+
+// Test that setting thread priorities doesn't cause a malfunction.
+// There's no easy way to verify the priority was set properly at this time.
+TEST(ThreadTest, Priorities) {
+ Thread *thread;
+ thread = new Thread();
+ EXPECT_TRUE(thread->SetPriority(PRIORITY_HIGH));
+ EXPECT_TRUE(thread->Start());
+ thread->Stop();
+ delete thread;
+ thread = new Thread();
+ EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+ EXPECT_TRUE(thread->Start());
+ thread->Stop();
+ delete thread;
+
+ thread = new Thread();
+ EXPECT_TRUE(thread->Start());
+#ifdef WIN32
+ EXPECT_TRUE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#else
+ EXPECT_FALSE(thread->SetPriority(PRIORITY_ABOVE_NORMAL));
+#endif
+ thread->Stop();
+ delete thread;
+
+}
+
+TEST(ThreadTest, Wrap) {
+ Thread* current_thread = ThreadManager::CurrentThread();
+ current_thread->UnwrapCurrent();
+ CustomThread* cthread = new CustomThread();
+ EXPECT_TRUE(cthread->WrapCurrent());
+ EXPECT_TRUE(cthread->started());
+ EXPECT_FALSE(cthread->IsOwned());
+ cthread->UnwrapCurrent();
+ EXPECT_FALSE(cthread->started());
+ delete cthread;
+ current_thread->WrapCurrent();
+}
+
+
+#ifdef WIN32
+class ComThreadTest : public testing::Test, public MessageHandler {
+ public:
+ ComThreadTest() : done_(false) {}
+ protected:
+ virtual void OnMessage(Message* message) {
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ // S_FALSE means the thread was already inited for a multithread apartment.
+ EXPECT_EQ(S_FALSE, hr);
+ if (SUCCEEDED(hr)) {
+ CoUninitialize();
+ }
+ done_ = true;
+ }
+ bool done_;
+};
+
+TEST_F(ComThreadTest, ComInited) {
+ Thread* thread = new ComThread();
+ EXPECT_TRUE(thread->Start());
+ thread->Post(this, 0);
+ EXPECT_TRUE_WAIT(done_, 1000);
+ delete thread;
+}
+#endif
diff --git a/talk/base/time_unittest.cc b/talk/base/time_unittest.cc
new file mode 100644
index 0000000..52e5111
--- /dev/null
+++ b/talk/base/time_unittest.cc
@@ -0,0 +1,136 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+TEST(TimeTest, Comparison) {
+ // Obtain two different times, in known order
+ TimeStamp ts_earlier = Time();
+ Thread::SleepMs(100);
+ TimeStamp ts_now = Time();
+ EXPECT_NE(ts_earlier, ts_now);
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_now));
+ EXPECT_TRUE( TimeIsLater( ts_earlier, ts_now));
+ EXPECT_FALSE(TimeIsLaterOrEqual(ts_now, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_now, ts_earlier));
+
+ // Edge cases
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_earlier, ts_earlier));
+
+ // Obtain a third time
+ TimeStamp ts_later = TimeAfter(100);
+ EXPECT_NE(ts_now, ts_later);
+ EXPECT_TRUE( TimeIsLater(ts_now, ts_later));
+ EXPECT_TRUE( TimeIsLater(ts_earlier, ts_later));
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_now, ts_later));
+ EXPECT_FALSE(TimeIsBetween(ts_earlier, ts_later, ts_now));
+ EXPECT_FALSE(TimeIsBetween(ts_now, ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsBetween(ts_now, ts_later, ts_earlier));
+ EXPECT_TRUE( TimeIsBetween(ts_later, ts_earlier, ts_now));
+ EXPECT_FALSE(TimeIsBetween(ts_later, ts_now, ts_earlier));
+
+ // Edge cases
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_earlier));
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsBetween(ts_earlier, ts_later, ts_later));
+
+ // Earlier of two times
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_now));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_now, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier));
+
+ // Later of two times
+ EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_now, TimeMax(ts_earlier, ts_now));
+ EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later));
+ EXPECT_EQ(ts_now, TimeMax(ts_now, ts_earlier));
+ EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier));
+}
+
+TEST(TimeTest, Intervals) {
+ TimeStamp ts_earlier = Time();
+ TimeStamp ts_later = TimeAfter(500);
+
+ // We can't depend on ts_later and ts_earlier to be exactly 500 apart
+ // since time elapses between the calls to Time() and TimeAfter(500)
+ EXPECT_LE(500, TimeDiff(ts_later, ts_earlier));
+ EXPECT_GE(-500, TimeDiff(ts_earlier, ts_later));
+
+ // Time has elapsed since ts_earlier
+ EXPECT_GE(TimeSince(ts_earlier), 0);
+
+ // ts_earlier is earlier than now, so TimeUntil ts_earlier is -ve
+ EXPECT_LE(TimeUntil(ts_earlier), 0);
+
+ // ts_later likely hasn't happened yet, so TimeSince could be -ve
+ // but within 500
+ EXPECT_GE(TimeSince(ts_later), -500);
+
+ // TimeUntil ts_later is at most 500
+ EXPECT_LE(TimeUntil(ts_later), 500);
+}
+
+TEST(TimeTest, BoundaryComparison) {
+ // Obtain two different times, in known order
+ TimeStamp ts_earlier = static_cast<TimeStamp>(-50);
+ TimeStamp ts_later = ts_earlier + 100;
+ EXPECT_NE(ts_earlier, ts_later);
+
+ // Common comparisons
+ EXPECT_TRUE( TimeIsLaterOrEqual(ts_earlier, ts_later));
+ EXPECT_TRUE( TimeIsLater( ts_earlier, ts_later));
+ EXPECT_FALSE(TimeIsLaterOrEqual(ts_later, ts_earlier));
+ EXPECT_FALSE(TimeIsLater( ts_later, ts_earlier));
+
+ // Earlier of two times
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_earlier, ts_later));
+ EXPECT_EQ(ts_earlier, TimeMin(ts_later, ts_earlier));
+
+ // Later of two times
+ EXPECT_EQ(ts_earlier, TimeMax(ts_earlier, ts_earlier));
+ EXPECT_EQ(ts_later, TimeMax(ts_earlier, ts_later));
+ EXPECT_EQ(ts_later, TimeMax(ts_later, ts_earlier));
+
+ // Interval
+ EXPECT_EQ(100, TimeDiff(ts_later, ts_earlier));
+ EXPECT_EQ(-100, TimeDiff(ts_earlier, ts_later));
+}
+
+} // namespace talk_base
diff --git a/talk/base/urlencode_unittest.cc b/talk/base/urlencode_unittest.cc
new file mode 100644
index 0000000..f71cd75
--- /dev/null
+++ b/talk/base/urlencode_unittest.cc
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2004--2011, 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 "talk/base/common.h"
+#include "talk/base/gunit.h"
+#include "talk/base/thread.h"
+#include "talk/base/urlencode.h"
+
+TEST(Urlencode, SourceTooLong) {
+ char source[] = "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
+ "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^";
+ char dest[1];
+ ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+ ASSERT_EQ('\0', dest[0]);
+
+ dest[0] = 'a';
+ ASSERT_EQ(0, UrlEncode(source, dest, 0));
+ ASSERT_EQ('a', dest[0]);
+}
+
+TEST(Urlencode, OneCharacterConversion) {
+ char source[] = "^";
+ char dest[4];
+ ASSERT_EQ(3, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+ ASSERT_STREQ("%5E", dest);
+}
+
+TEST(Urlencode, ShortDestinationNoEncoding) {
+ // In this case we have a destination that would not be
+ // big enough to hold an encoding but is big enough to
+ // hold the text given.
+ char source[] = "aa";
+ char dest[3];
+ ASSERT_EQ(2, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+ ASSERT_STREQ("aa", dest);
+}
+
+TEST(Urlencode, ShortDestinationEncoding) {
+ // In this case we have a destination that is not
+ // big enough to hold the encoding.
+ char source[] = "&";
+ char dest[3];
+ ASSERT_EQ(0, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+ ASSERT_EQ('\0', dest[0]);
+}
+
+TEST(Urlencode, Encoding1) {
+ char source[] = "A^ ";
+ char dest[8];
+ ASSERT_EQ(5, UrlEncode(source, dest, ARRAY_SIZE(dest)));
+ ASSERT_STREQ("A%5E+", dest);
+}
+
+TEST(Urlencode, Encoding2) {
+ char source[] = "A^ ";
+ char dest[8];
+ ASSERT_EQ(7, UrlEncodeWithoutEncodingSpaceAsPlus(source, dest,
+ ARRAY_SIZE(dest)));
+ ASSERT_STREQ("A%5E%20", dest);
+}
+
+TEST(Urldecode, Decoding1) {
+ char source[] = "A%5E+";
+ char dest[8];
+ ASSERT_EQ(3, UrlDecode(source, dest));
+ ASSERT_STREQ("A^ ", dest);
+}
+
+TEST(Urldecode, Decoding2) {
+ char source[] = "A%5E+";
+ char dest[8];
+ ASSERT_EQ(3, UrlDecodeWithoutEncodingSpaceAsPlus(source, dest));
+ ASSERT_STREQ("A^+", dest);
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
index a698f36..c3a6490 100644
--- a/talk/examples/call/callclient.cc
+++ b/talk/examples/call/callclient.cc
@@ -54,6 +54,7 @@
#include "talk/session/phone/mediasessionclient.h"
#include "talk/session/phone/videorendererfactory.h"
#include "talk/xmpp/constants.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
#include "talk/xmpp/mucroomconfigtask.h"
#include "talk/xmpp/mucroomlookuptask.h"
@@ -94,11 +95,23 @@
const char* CALL_COMMANDS =
"Available commands:\n"
"\n"
-" hangup Ends the call.\n"
-" mute Stops sending voice.\n"
-" unmute Re-starts sending voice.\n"
-" dtmf Sends a DTMF tone.\n"
-" quit Quits the application.\n"
+" hangup Ends the call.\n"
+" mute Stops sending voice.\n"
+" unmute Re-starts sending voice.\n"
+" dtmf Sends a DTMF tone.\n"
+" quit Quits the application.\n"
+"";
+
+// TODO: Make present and record really work.
+const char* HANGOUT_COMMANDS =
+"Available MUC commands:\n"
+"\n"
+" present Starts presenting (just signalling; not actually presenting.)\n"
+" unpresent Stops presenting (just signalling; not actually presenting.)\n"
+" record Starts recording (just signalling; not actually recording.)\n"
+" unrecord Stops recording (just signalling; not actually recording.)\n"
+" rmute [nick] Remote mute another participant.\n"
+" quit Quits the application.\n"
"";
const char* RECEIVE_COMMANDS =
@@ -169,13 +182,43 @@
call_->Terminate();
} else if (command == "mute") {
call_->Mute(true);
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishAudioMuteState(true);
+ }
} else if (command == "unmute") {
call_->Mute(false);
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishAudioMuteState(false);
+ }
+ } else if (command == "present") {
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishPresenterState(true);
+ }
+ } else if (command == "unpresent") {
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishPresenterState(false);
+ }
+ } else if (command == "record") {
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishRecordingState(true);
+ }
+ } else if (command == "unrecord") {
+ if (InMuc()) {
+ hangout_pubsub_client_->PublishRecordingState(false);
+ }
+ } else if ((command == "rmute") && (words.size() == 2)) {
+ if (InMuc()) {
+ const std::string& nick = words[1];
+ hangout_pubsub_client_->RemoteMute(nick);
+ }
} else if ((command == "dtmf") && (words.size() == 2)) {
int ev = std::string("0123456789*#").find(words[1][0]);
call_->PressDTMF(ev);
} else {
console_->PrintLine(CALL_COMMANDS);
+ if (InMuc()) {
+ console_->PrintLine(HANGOUT_COMMANDS);
+ }
}
} else {
if (command == "roster") {
@@ -231,6 +274,7 @@
media_engine_(NULL),
media_client_(NULL),
call_(NULL),
+ hangout_pubsub_client_(NULL),
incoming_call_(false),
auto_accept_(false),
pmuc_domain_("groupchat.google.com"),
@@ -295,6 +339,8 @@
console_->PrintLine("call destroyed");
call_ = NULL;
session_ = NULL;
+ delete hangout_pubsub_client_;
+ hangout_pubsub_client_ = NULL;
}
}
@@ -366,7 +412,7 @@
xmpp_client_->jid(),
session_manager_,
media_engine_,
- new cricket::DeviceManager());
+ cricket::DeviceManagerFactory::Create());
media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
media_client_->SignalCallDestroy.connect(this, &CallClient::OnCallDestroy);
media_client_->SignalDevicesChange.connect(this,
@@ -615,6 +661,95 @@
call_->SetVideoRenderer(session_, 0, remote_renderer_);
}
}
+ if (options.is_muc) {
+ const std::string& nick = mucs_[jid]->local_jid().resource();
+ hangout_pubsub_client_ =
+ new buzz::HangoutPubSubClient(xmpp_client_, jid, nick);
+ hangout_pubsub_client_->SignalPresenterStateChange.connect(
+ this, &CallClient::OnPresenterStateChange);
+ hangout_pubsub_client_->SignalAudioMuteStateChange.connect(
+ this, &CallClient::OnAudioMuteStateChange);
+ hangout_pubsub_client_->SignalRecordingStateChange.connect(
+ this, &CallClient::OnRecordingStateChange);
+ hangout_pubsub_client_->SignalRemoteMute.connect(
+ this, &CallClient::OnRemoteMuted);
+ hangout_pubsub_client_->SignalRequestError.connect(
+ this, &CallClient::OnHangoutRequestError);
+ hangout_pubsub_client_->SignalPublishAudioMuteError.connect(
+ this, &CallClient::OnHangoutPublishAudioMuteError);
+ hangout_pubsub_client_->SignalPublishPresenterError.connect(
+ this, &CallClient::OnHangoutPublishPresenterError);
+ hangout_pubsub_client_->SignalPublishRecordingError.connect(
+ this, &CallClient::OnHangoutPublishRecordingError);
+ hangout_pubsub_client_->SignalRemoteMuteError.connect(
+ this, &CallClient::OnHangoutRemoteMuteError);
+ }
+}
+
+void CallClient::OnPresenterStateChange(
+ const std::string& nick, bool was_presenting, bool is_presenting) {
+ if (!was_presenting && is_presenting) {
+ console_->PrintLine("%s now presenting.", nick.c_str());
+ } else if (was_presenting && !is_presenting) {
+ console_->PrintLine("%s no longer presenting.", nick.c_str());
+ }
+}
+
+void CallClient::OnAudioMuteStateChange(
+ const std::string& nick, bool was_muted, bool is_muted) {
+ if (!was_muted && is_muted) {
+ console_->PrintLine("%s now muted.", nick.c_str());
+ } else if (was_muted && !is_muted) {
+ console_->PrintLine("%s no longer muted.", nick.c_str());
+ }
+}
+
+void CallClient::OnRecordingStateChange(
+ const std::string& nick, bool was_recording, bool is_recording) {
+ if (!was_recording && is_recording) {
+ console_->PrintLine("%s now recording.", nick.c_str());
+ } else if (was_recording && is_recording) {
+ console_->PrintLine("%s no longer recording.", nick.c_str());
+ }
+}
+
+void CallClient::OnRemoteMuted(const std::string& mutee_nick,
+ const std::string& muter_nick,
+ bool should_mute_locally) {
+ if (should_mute_locally) {
+ call_->Mute(true);
+ console_->PrintLine("Remote muted by %s.", muter_nick.c_str());
+ } else {
+ console_->PrintLine("%s remote muted by %s.",
+ mutee_nick.c_str(), muter_nick.c_str());
+ }
+}
+
+void CallClient::OnHangoutRequestError(const std::string& node,
+ const buzz::XmlElement* stanza) {
+ console_->PrintLine("Failed request pub sub items for node %s.",
+ node.c_str());
+}
+
+void CallClient::OnHangoutPublishAudioMuteError(
+ const std::string& task_id, const buzz::XmlElement* stanza) {
+ console_->PrintLine("Failed to publish audio mute state.");
+}
+
+void CallClient::OnHangoutPublishPresenterError(
+ const std::string& task_id, const buzz::XmlElement* stanza) {
+ console_->PrintLine("Failed to publish presenting state.");
+}
+
+void CallClient::OnHangoutPublishRecordingError(
+ const std::string& task_id, const buzz::XmlElement* stanza) {
+ console_->PrintLine("Failed to publish recording state.");
+}
+
+void CallClient::OnHangoutRemoteMuteError(const std::string& task_id,
+ const std::string& mutee_nick,
+ const buzz::XmlElement* stanza) {
+ console_->PrintLine("Failed to remote mute.");
}
void CallClient::CallVoicemail(const std::string& name) {
@@ -624,7 +759,7 @@
return;
}
buzz::VoicemailJidRequester *request =
- new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
+ new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
request->SignalGotVoicemailJid.connect(this,
&CallClient::OnFoundVoicemailJid);
request->SignalVoicemailJidError.connect(this,
@@ -847,13 +982,21 @@
}
}
+bool CallClient::InMuc() {
+ return FirstMucJid().IsValid();
+}
+
+const buzz::Jid& CallClient::FirstMucJid() {
+ return mucs_.begin()->first;
+}
+
void CallClient::LeaveMuc(const std::string& room) {
buzz::Jid room_jid;
if (room.length() > 0) {
room_jid = buzz::Jid(room);
} else if (mucs_.size() > 0) {
// leave the first MUC if no JID specified
- room_jid = mucs_.begin()->first;
+ room_jid = FirstMucJid();
}
if (!room_jid.IsValid()) {
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
index 18296be..568a818 100644
--- a/talk/examples/call/callclient.h
+++ b/talk/examples/call/callclient.h
@@ -36,6 +36,7 @@
#include "talk/session/phone/mediachannel.h"
#include "talk/session/phone/mediamessages.h"
#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/hangoutpubsubclient.h"
#include "talk/xmpp/xmppclient.h"
#include "talk/examples/call/status.h"
#include "talk/examples/call/console.h"
@@ -55,6 +56,7 @@
class MucRoomLookupTask;
class MucStatus;
class XmlElement;
+class HangoutPubSubClient;
struct AvailableMediaEntry;
struct MucRoomInfo;
}
@@ -124,6 +126,8 @@
void JoinMuc(const std::string& room_jid_str);
void LookupAndJoinMuc(const std::string& room_name);
void InviteToMuc(const std::string& user, const std::string& room);
+ bool InMuc();
+ const buzz::Jid& FirstMucJid();
void LeaveMuc(const std::string& room);
void SetNick(const std::string& muc_nick);
void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
@@ -167,6 +171,26 @@
void OnMucJoined(const buzz::Jid& endpoint);
void OnMucStatusUpdate(const buzz::Jid& jid, const buzz::MucStatus& status);
void OnMucLeft(const buzz::Jid& endpoint, int error);
+ void OnPresenterStateChange(const std::string& nick,
+ bool was_presenting, bool is_presenting);
+ void OnAudioMuteStateChange(const std::string& nick,
+ bool was_muted, bool is_muted);
+ void OnRecordingStateChange(const std::string& nick,
+ bool was_recording, bool is_recording);
+ void OnRemoteMuted(const std::string& mutee_nick,
+ const std::string& muter_nick,
+ bool should_mute_locally);
+ void OnHangoutRequestError(const std::string& node,
+ const buzz::XmlElement* stanza);
+ void OnHangoutPublishAudioMuteError(const std::string& task_id,
+ const buzz::XmlElement* stanza);
+ void OnHangoutPublishPresenterError(const std::string& task_id,
+ const buzz::XmlElement* stanza);
+ void OnHangoutPublishRecordingError(const std::string& task_id,
+ const buzz::XmlElement* stanza);
+ void OnHangoutRemoteMuteError(const std::string& task_id,
+ const std::string& mutee_nick,
+ const buzz::XmlElement* stanza);
void OnDevicesChange();
void OnFoundVoicemailJid(const buzz::Jid& to, const buzz::Jid& voicemail);
void OnVoicemailJidError(const buzz::Jid& to);
@@ -224,6 +248,7 @@
cricket::Call* call_;
cricket::Session *session_;
+ buzz::HangoutPubSubClient* hangout_pubsub_client_;
bool incoming_call_;
bool auto_accept_;
std::string pmuc_domain_;
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
index e053a8f..5e029b0 100644
--- a/talk/libjingle.scons
+++ b/talk/libjingle.scons
@@ -138,6 +138,7 @@
"base/httpclient.cc",
"base/httpcommon.cc",
"base/httprequest.cc",
+ "base/httpserver.cc",
"base/logging.cc",
"base/md5c.c",
"base/messagehandler.cc",
@@ -226,9 +227,13 @@
"xmpp/constants.cc",
"xmpp/iqtask.cc",
"xmpp/jid.cc",
+ "xmpp/pubsubtasks.cc",
+ "xmpp/pubsubclient.cc",
+ "xmpp/hangoutpubsubclient.cc",
"xmpp/mucroomconfigtask.cc",
"xmpp/mucroomlookuptask.cc",
"xmpp/ratelimitmanager.cc",
+ "xmpp/receivetask.cc",
"xmpp/saslmechanism.cc",
"xmpp/xmppclient.cc",
"xmpp/xmppengineimpl.cc",
@@ -255,6 +260,9 @@
"base/winping.cc",
"session/phone/gdivideorenderer.cc",
],
+ mac_ccflags = [
+ "-Wno-deprecated-declarations",
+ ],
extra_srcs = [
"base/json.cc",
],
@@ -373,12 +381,34 @@
"p2p/base/stunserver_main.cc",
],
)
-talk.Unittest(env, name = "network",
+talk.Unittest(env, name = "base",
libs = [
"jingle",
],
srcs = [
+ "base/asynchttprequest_unittest.cc",
+ "base/autodetectproxy_unittest.cc",
+ "base/bytebuffer_unittest.cc",
+ "base/event_unittest.cc",
+ "base/fileutils_unittest.cc",
+ "base/helpers_unittest.cc",
+ "base/host_unittest.cc",
+ "base/httpbase_unittest.cc",
+ "base/httpcommon_unittest.cc",
+ "base/httpserver_unittest.cc",
+ "base/logging_unittest.cc",
+ "base/messagequeue_unittest.cc",
"base/network_unittest.cc",
+ "base/referencecountedsingletonfactory_unittest.cc",
+ "base/signalthread_unittest.cc",
+ "base/socketaddress_unittest.cc",
+ "base/stream_unittest.cc",
+ "base/stringencode_unittest.cc",
+ "base/stringutils_unittest.cc",
+ "base/task_unittest.cc",
+ "base/thread_unittest.cc",
+ "base/time_unittest.cc",
+ "base/urlencode_unittest.cc",
],
includedirs = [
"third_party/gtest/include",
diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h
index ea1e4f0..c9008d4 100644
--- a/talk/p2p/client/sessionsendtask.h
+++ b/talk/p2p/client/sessionsendtask.h
@@ -108,10 +108,6 @@
}
virtual int ProcessResponse() {
- if (parent_->GetState() != buzz::XmppEngine::STATE_OPEN) {
- return STATE_DONE;
- }
-
const buzz::XmlElement* next = NextStanza();
if (next == NULL)
return STATE_BLOCKED;
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
index 97419cb..98b8ded 100644
--- a/talk/session/phone/channelmanager.cc
+++ b/talk/session/phone/channelmanager.cc
@@ -132,12 +132,12 @@
ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
: media_engine_(MediaEngineFactory::Create()),
- device_manager_(new DeviceManager()),
+ device_manager_(cricket::DeviceManagerFactory::Create()),
initialized_(false),
main_thread_(talk_base::Thread::Current()),
worker_thread_(worker_thread),
- audio_in_device_(DeviceManager::kDefaultDeviceName),
- audio_out_device_(DeviceManager::kDefaultDeviceName),
+ audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
+ audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
local_renderer_(NULL),
capturing_(false),
@@ -145,15 +145,16 @@
Construct();
}
-ChannelManager::ChannelManager(MediaEngineInterface* me, DeviceManager* dm,
+ChannelManager::ChannelManager(MediaEngineInterface* me,
+ DeviceManagerInterface* dm,
talk_base::Thread* worker_thread)
: media_engine_(me),
device_manager_(dm),
initialized_(false),
main_thread_(talk_base::Thread::Current()),
worker_thread_(worker_thread),
- audio_in_device_(DeviceManager::kDefaultDeviceName),
- audio_out_device_(DeviceManager::kDefaultDeviceName),
+ audio_in_device_(DeviceManagerInterface::kDefaultDeviceName),
+ audio_out_device_(DeviceManagerInterface::kDefaultDeviceName),
audio_options_(MediaEngineInterface::DEFAULT_AUDIO_OPTIONS),
local_renderer_(NULL),
capturing_(false),
@@ -166,7 +167,7 @@
SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
device_manager_->Init();
// Set camera_device_ to the name of the default video capturer.
- SetVideoOptions(DeviceManager::kDefaultDeviceName);
+ SetVideoOptions(DeviceManagerInterface::kDefaultDeviceName);
// Camera is started asynchronously, request callbacks when startup
// completes to be able to forward them to the rendering manager.
@@ -227,19 +228,19 @@
if (!device_manager_->GetAudioInputDevice(audio_in_device_, &device)) {
LOG(LS_WARNING) << "The preferred microphone '" << audio_in_device_
<< "' is unavailable. Fall back to the default.";
- audio_in_device_ = DeviceManager::kDefaultDeviceName;
+ audio_in_device_ = DeviceManagerInterface::kDefaultDeviceName;
}
if (!device_manager_->GetAudioOutputDevice(audio_out_device_, &device)) {
LOG(LS_WARNING) << "The preferred speaker '" << audio_out_device_
<< "' is unavailable. Fall back to the default.";
- audio_out_device_ = DeviceManager::kDefaultDeviceName;
+ audio_out_device_ = DeviceManagerInterface::kDefaultDeviceName;
}
if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
if (!camera_device_.empty()) {
LOG(LS_WARNING) << "The preferred camera '" << camera_device_
<< "' is unavailable. Fall back to the default.";
}
- camera_device_ = DeviceManager::kDefaultDeviceName;
+ camera_device_ = DeviceManagerInterface::kDefaultDeviceName;
}
if (!SetAudioOptions(audio_in_device_, audio_out_device_,
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
index cada408..c574a43 100644
--- a/talk/session/phone/channelmanager.h
+++ b/talk/session/phone/channelmanager.h
@@ -58,7 +58,7 @@
explicit ChannelManager(talk_base::Thread* worker);
// For testing purposes. Allows the media engine and dev manager to be mocks.
// The ChannelManager takes ownership of these objects.
- ChannelManager(MediaEngineInterface* me, DeviceManager* dm,
+ ChannelManager(MediaEngineInterface* me, DeviceManagerInterface* dm,
talk_base::Thread* worker);
~ChannelManager();
@@ -181,7 +181,7 @@
talk_base::CriticalSection crit_;
talk_base::scoped_ptr<MediaEngineInterface> media_engine_;
- talk_base::scoped_ptr<DeviceManager> device_manager_;
+ talk_base::scoped_ptr<DeviceManagerInterface> device_manager_;
bool initialized_;
talk_base::Thread* main_thread_;
talk_base::Thread* worker_thread_;
diff --git a/talk/session/phone/codec.cc b/talk/session/phone/codec.cc
index 01d1a7b..a200ca4 100644
--- a/talk/session/phone/codec.cc
+++ b/talk/session/phone/codec.cc
@@ -26,28 +26,33 @@
*/
#include "talk/session/phone/codec.h"
+
#include <sstream>
+#include "talk/base/stringutils.h"
+
namespace cricket {
static const int kMaxStaticPayloadId = 95;
bool AudioCodec::Matches(int payload, const std::string& nm) const {
// Match the codec id/name based on the typical static/dynamic name rules.
- return (payload <= kMaxStaticPayloadId) ? (id == payload) : (name == nm);
+ // Matching is case-insensitive.
+ return (payload <= kMaxStaticPayloadId) ?
+ (id == payload) : (_stricmp(name.c_str(), nm.c_str()) == 0);
}
bool AudioCodec::Matches(const AudioCodec& codec) const {
// If a nonzero clockrate is specified, it must match the actual clockrate.
// If a nonzero bitrate is specified, it must match the actual bitrate,
- // unless the codec is VBR (-1), where we just force the supplied value.
+ // unless the codec is VBR (0), where we just force the supplied value.
// The number of channels must match exactly.
// Preference is ignored.
// TODO: Treat a zero clockrate as 8000Hz, the RTP default clockrate.
return Matches(codec.id, codec.name) &&
((codec.clockrate == 0 /*&& clockrate == 8000*/) ||
clockrate == codec.clockrate) &&
- (codec.bitrate == 0 || bitrate == -1 || bitrate == codec.bitrate) &&
+ (codec.bitrate == 0 || bitrate <= 0 || bitrate == codec.bitrate) &&
(codec.channels == 0 || channels == codec.channels);
}
@@ -60,7 +65,9 @@
bool VideoCodec::Matches(int payload, const std::string& nm) const {
// Match the codec id/name based on the typical static/dynamic name rules.
- return (payload <= kMaxStaticPayloadId) ? (id == payload) : (name == nm);
+ // Matching is case-insensitive.
+ return (payload <= kMaxStaticPayloadId) ?
+ (id == payload) : (_stricmp(name.c_str(), nm.c_str()) == 0);
}
bool VideoCodec::Matches(const VideoCodec& codec) const {
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
index 376b0e9..a464dd1 100644
--- a/talk/session/phone/devicemanager.cc
+++ b/talk/session/phone/devicemanager.cc
@@ -65,7 +65,11 @@
namespace cricket {
// Initialize to empty string.
-const char DeviceManager::kDefaultDeviceName[] = "";
+const char DeviceManagerInterface::kDefaultDeviceName[] = "";
+
+DeviceManagerInterface* DeviceManagerFactory::Create() {
+ return new DeviceManager();
+}
#ifdef WIN32
class DeviceWatcher : public talk_base::Win32Window {
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
index 04e494e..38776fa 100644
--- a/talk/session/phone/devicemanager.h
+++ b/talk/session/phone/devicemanager.h
@@ -55,9 +55,42 @@
std::string id;
};
-// DeviceManager manages the audio and video devices on the system.
-// Methods are virtual to allow for easy stubbing/mocking in tests.
-class DeviceManager {
+// DeviceManagerInterface - interface to manage the audio and
+// video devices on the system.
+class DeviceManagerInterface {
+ public:
+ virtual ~DeviceManagerInterface() { }
+
+ // Initialization
+ virtual bool Init() = 0;
+ virtual void Terminate() = 0;
+
+ // Capabilities
+ virtual int GetCapabilities() = 0;
+
+ // Device enumeration
+ virtual bool GetAudioInputDevices(std::vector<Device>* devices) = 0;
+ virtual bool GetAudioOutputDevices(std::vector<Device>* devices) = 0;
+
+ virtual bool GetAudioInputDevice(const std::string& name, Device* out) = 0;
+ virtual bool GetAudioOutputDevice(const std::string& name, Device* out) = 0;
+
+ virtual bool GetVideoCaptureDevices(std::vector<Device>* devs) = 0;
+ virtual bool GetVideoCaptureDevice(const std::string& name, Device* out) = 0;
+
+ sigslot::signal0<> SignalDevicesChange;
+
+ static const char kDefaultDeviceName[];
+};
+
+class DeviceManagerFactory {
+ public:
+ static DeviceManagerInterface* Create();
+ private:
+ DeviceManagerFactory();
+};
+
+class DeviceManager : public DeviceManagerInterface {
public:
DeviceManager();
virtual ~DeviceManager();
@@ -65,7 +98,6 @@
// Initialization
virtual bool Init();
virtual void Terminate();
- bool initialized() const { return initialized_; }
// Capabilities
virtual int GetCapabilities();
@@ -74,18 +106,15 @@
virtual bool GetAudioInputDevices(std::vector<Device>* devices);
virtual bool GetAudioOutputDevices(std::vector<Device>* devices);
- bool GetAudioInputDevice(const std::string& name, Device* out);
- bool GetAudioOutputDevice(const std::string& name, Device* out);
+ virtual bool GetAudioInputDevice(const std::string& name, Device* out);
+ virtual bool GetAudioOutputDevice(const std::string& name, Device* out);
virtual bool GetVideoCaptureDevices(std::vector<Device>* devs);
- bool GetVideoCaptureDevice(const std::string& name, Device* out);
+ virtual bool GetVideoCaptureDevice(const std::string& name, Device* out);
- sigslot::signal0<> SignalDevicesChange;
-
+ bool initialized() const { return initialized_; }
void OnDevicesChange() { SignalDevicesChange(); }
- static const char kDefaultDeviceName[];
-
protected:
virtual bool GetAudioDevice(bool is_input, const std::string& name,
Device* out);
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
index 5190232..51e3b8b 100644
--- a/talk/session/phone/mediaengine.cc
+++ b/talk/session/phone/mediaengine.cc
@@ -30,18 +30,31 @@
#if defined(HAVE_LINPHONE)
#include "talk/session/phone/linphonemediaengine.h"
#else
+#if defined(HAVE_WEBRTC_VOICE)
+#include "talk/session/phone/webrtcvoiceengine.h"
+#endif // HAVE_WEBRTC_VOICE
+#if defined(HAVE_WEBRTC_VIDEO)
+#include "talk/session/phone/webrtcvideoengine.h"
+#endif // HAVE_WEBRTC_VIDEO
#endif // HAVE_LINPHONE
namespace cricket {
+#if defined(HAVE_WEBRTC_VOICE)
+#define AUDIO_ENG_NAME WebRtcVoiceEngine
+#endif
+
+#if defined(HAVE_WEBRTC_VIDEO)
+#define VIDEO_ENG_NAME WebRtcVideoEngine
+#endif
MediaEngineInterface* MediaEngineFactory::Create() {
#if defined(HAVE_LINPHONE)
return new LinphoneMediaEngine("", "");
-#elif defined(ANDROID)
- return AndroidMediaEngineFactory::Create();
+#elif defined(AUDIO_ENG_NAME) && defined(VIDEO_ENG_NAME)
+ return new CompositeMediaEngine<AUDIO_ENG_NAME, VIDEO_ENG_NAME>();
#else
return new NullMediaEngine();
-#endif // HAVE_LINPHONE ANDROID HAVE_LMI HAVE_GIPS HAVE_WEBRTC_VOICE/VIDEO
+#endif
}
}; // namespace cricket
diff --git a/talk/session/phone/mediasession.cc b/talk/session/phone/mediasession.cc
index d70ab16..8102d2a 100644
--- a/talk/session/phone/mediasession.cc
+++ b/talk/session/phone/mediasession.cc
@@ -56,6 +56,7 @@
return true;
}
+#ifdef HAVE_SRTP
static bool AddCryptoParams(const std::string& cipher_suite,
CryptoParamsVec *out) {
int size = out->size();
@@ -63,6 +64,7 @@
out->resize(size + 1);
return CreateCryptoParams(size, cipher_suite, &out->at(size));
}
+#endif
// For audio, HMAC 32 is prefered because of the low overhead.
static bool GetSupportedAudioCryptos(CryptoParamsVec* cryptos) {
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
index e709479..3491d26 100644
--- a/talk/session/phone/mediasessionclient.cc
+++ b/talk/session/phone/mediasessionclient.cc
@@ -55,7 +55,7 @@
MediaSessionClient::MediaSessionClient(
const buzz::Jid& jid, SessionManager *manager,
- MediaEngineInterface* media_engine, DeviceManager* device_manager)
+ MediaEngineInterface* media_engine, DeviceManagerInterface* device_manager)
: jid_(jid),
session_manager_(manager),
focus_call_(NULL),
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
index 4616589..c5c455f 100644
--- a/talk/session/phone/mediasessionclient.h
+++ b/talk/session/phone/mediasessionclient.h
@@ -56,7 +56,7 @@
// and device_manager.
MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
MediaEngineInterface* media_engine,
- DeviceManager* device_manager);
+ DeviceManagerInterface* device_manager);
~MediaSessionClient();
const buzz::Jid &jid() const { return jid_; }
diff --git a/talk/session/phone/v4llookup.cc b/talk/session/phone/v4llookup.cc
index e8436e1..f68d219 100644
--- a/talk/session/phone/v4llookup.cc
+++ b/talk/session/phone/v4llookup.cc
@@ -22,7 +22,7 @@
namespace cricket {
-V4LLookup *V4LLookup::v4l_lookup_ = new V4LLookup();
+V4LLookup *V4LLookup::v4l_lookup_ = NULL;
bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) {
// check device major/minor numbers are in the range for video devices.
diff --git a/talk/session/phone/v4llookup.h b/talk/session/phone/v4llookup.h
index 1150172..966d6c5 100644
--- a/talk/session/phone/v4llookup.h
+++ b/talk/session/phone/v4llookup.h
@@ -14,27 +14,31 @@
#ifdef LINUX
namespace cricket {
class V4LLookup {
- public:
- virtual ~V4LLookup() {}
+ public:
+ virtual ~V4LLookup() {}
- static bool IsV4L2Device(const std::string& device_path) {
- return GetV4LLookup()->CheckIsV4L2Device(device_path);
+ static bool IsV4L2Device(const std::string& device_path) {
+ return GetV4LLookup()->CheckIsV4L2Device(device_path);
+ }
+
+ static void SetV4LLookup(V4LLookup* v4l_lookup) {
+ v4l_lookup_ = v4l_lookup;
+ }
+
+ static V4LLookup* GetV4LLookup() {
+ if (!v4l_lookup_) {
+ v4l_lookup_ = new V4LLookup();
}
+ return v4l_lookup_;
+ }
- static void SetV4LLookup(V4LLookup* v4l_lookup) {
- v4l_lookup_ = v4l_lookup;
- }
-
- static V4LLookup* GetV4LLookup() {
- return v4l_lookup_;
- }
-
- protected:
- static V4LLookup* v4l_lookup_;
- // Making virtual so it is easier to mock
- virtual bool CheckIsV4L2Device(const std::string& device_path);
-
+ protected:
+ static V4LLookup* v4l_lookup_;
+ // Making virtual so it is easier to mock
+ virtual bool CheckIsV4L2Device(const std::string& device_path);
};
+
} // namespace cricket
-#endif
+
+#endif // LINUX
#endif // TALK_SESSION_PHONE_V4LLOOKUP_H_
diff --git a/talk/session/phone/webrtccommon.h b/talk/session/phone/webrtccommon.h
index 60fee27..286b049 100644
--- a/talk/session/phone/webrtccommon.h
+++ b/talk/session/phone/webrtccommon.h
@@ -31,12 +31,8 @@
#ifdef WEBRTC_RELATIVE_PATH
#include "common_types.h"
-#include "video_engine/main/interface/vie_base.h"
-#include "voice_engine/main/interface/voe_base.h"
#else
#include "third_party/webrtc/files/include/common_types.h"
-#include "third_party/webrtc/files/include/voe_base.h"
-#include "third_party/webrtc/files/include/vie_base.h"
#endif // WEBRTC_RELATIVE_PATH
namespace cricket {
diff --git a/talk/session/phone/webrtcmediaengine.h b/talk/session/phone/webrtcmediaengine.h
new file mode 100644
index 0000000..2173d14
--- /dev/null
+++ b/talk/session/phone/webrtcmediaengine.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
+
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/webrtcvideoengine.h"
+#include "talk/session/phone/webrtcvoiceengine.h"
+
+namespace webrtc {
+class AudioDeviceModule;
+class VideoCaptureModule;
+}
+
+namespace cricket {
+
+typedef CompositeMediaEngine<WebRtcVoiceEngine, WebRtcVideoEngine>
+ WebRtcCompositeMediaEngine;
+
+class WebRtcMediaEngine : public WebRtcCompositeMediaEngine {
+ public:
+ WebRtcMediaEngine(webrtc::AudioDeviceModule* adm,
+ webrtc::AudioDeviceModule* adm_sc, webrtc::VideoCaptureModule* vcm) {
+ voice_.SetAudioDeviceModule(adm, adm_sc);
+ video_.SetVoiceEngine(&voice_);
+ video_.SetCaptureModule(vcm);
+ video_.EnableTimedRender();
+ }
+ // Allow the VCM be set later if not ready during the construction time
+ bool SetVideoCaptureModule(webrtc::VideoCaptureModule* vcm) {
+ return video_.SetCaptureModule(vcm);
+ }
+};
+
+} // namespace cricket
+#endif // TALK_SESSION_PHONE_WEBRTCMEDIAENGINE_H_
diff --git a/talk/session/phone/webrtcvideoengine.cc b/talk/session/phone/webrtcvideoengine.cc
index 6f9ff6a..a65c264 100644
--- a/talk/session/phone/webrtcvideoengine.cc
+++ b/talk/session/phone/webrtcvideoengine.cc
@@ -62,15 +62,12 @@
return renderer_->SetSize(width_, height_, 0) ? 0 : -1;
}
- virtual int DeliverFrame(unsigned char* buffer, int buffer_size) {
+ virtual int DeliverFrame(unsigned char* buffer, int buffer_size,
+ unsigned int time_stamp) {
if (renderer_ == NULL)
return 0;
WebRtcVideoFrame video_frame;
- // TODO: Currently by the time DeliverFrame got called,
- // ViE expects the frame will be rendered ASAP. However, the libjingle
- // renderer may have its own internal delays. Can you disable the buffering
- // inside ViE and surface the timing information to this callback?
- video_frame.Attach(buffer, buffer_size, width_, height_, 0, 0);
+ video_frame.Attach(buffer, buffer_size, width_, height_, 0, time_stamp);
int ret = renderer_->RenderFrame(&video_frame) ? 0 : -1;
uint8* buffer_temp;
size_t buffer_size_temp;
@@ -117,38 +114,24 @@
WebRtcVideoEngine::WebRtcVideoEngine()
: vie_wrapper_(new ViEWrapper()),
- capture_module_(NULL),
- external_capture_(false),
- render_module_(new WebRtcPassthroughRender()),
voice_engine_(NULL) {
Construct();
}
WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
- webrtc::VideoCaptureModule* capture)
- : vie_wrapper_(new ViEWrapper()),
- capture_module_(capture),
- external_capture_(true),
- render_module_(webrtc::VideoRender::CreateVideoRender(0, NULL, false,
- webrtc::kRenderExternal)),
- voice_engine_(voice_engine) {
- Construct();
-}
-
-WebRtcVideoEngine::WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
ViEWrapper* vie_wrapper)
: vie_wrapper_(vie_wrapper),
- capture_module_(NULL),
- external_capture_(false),
- render_module_(new WebRtcPassthroughRender()),
voice_engine_(voice_engine) {
Construct();
}
void WebRtcVideoEngine::Construct() {
+ initialized_ = false;
capture_id_ = -1;
+ capture_module_ = NULL;
log_level_ = kDefaultLogSeverity;
capture_started_ = false;
+ render_module_.reset(new WebRtcPassthroughRender());
ApplyLogging();
if (vie_wrapper_->engine()->SetTraceCallback(this) != 0) {
@@ -220,6 +203,8 @@
std::sort(video_codecs_.begin(), video_codecs_.end(),
&VideoCodec::Preferable);
+
+ initialized_ = true;
return true;
}
@@ -304,6 +289,7 @@
void WebRtcVideoEngine::Terminate() {
LOG(LS_INFO) << "WebRtcVideoEngine::Terminate";
+ initialized_ = false;
SetCapture(false);
if (local_renderer_.get()) {
// If the renderer already set, stop it first
@@ -590,6 +576,25 @@
}
}
+bool WebRtcVideoEngine::SetVoiceEngine(WebRtcVoiceEngine* voice_engine) {
+ if (initialized_) {
+ LOG(LS_WARNING) << "SetVoiceEngine can not be called after Init.";
+ return false;
+ }
+ voice_engine_ = voice_engine;
+ return true;
+}
+
+bool WebRtcVideoEngine::EnableTimedRender() {
+ if (initialized_) {
+ LOG(LS_WARNING) << "EnableTimedRender can not be called after Init.";
+ return false;
+ }
+ render_module_.reset(webrtc::VideoRender::CreateVideoRender(0, NULL,
+ false, webrtc::kRenderExternal));
+ return true;
+}
+
// WebRtcVideoMediaChannel
WebRtcVideoMediaChannel::WebRtcVideoMediaChannel(
diff --git a/talk/session/phone/webrtcvideoengine.h b/talk/session/phone/webrtcvideoengine.h
index 8daa81c..dc69a83 100644
--- a/talk/session/phone/webrtcvideoengine.h
+++ b/talk/session/phone/webrtcvideoengine.h
@@ -35,6 +35,11 @@
#include "talk/session/phone/codec.h"
#include "talk/session/phone/channel.h"
#include "talk/session/phone/webrtccommon.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "voice_engine/main/interface/vie_base.h"
+#else
+#include "third_party/webrtc/files/include/vie_base.h"
+#endif // WEBRTC_RELATIVE_PATH
namespace webrtc {
class VideoCaptureModule;
@@ -56,10 +61,6 @@
public:
// Creates the WebRtcVideoEngine with internal VideoCaptureModule.
WebRtcVideoEngine();
- // Creates the WebRtcVideoEngine, and specifies the WebRtcVoiceEngine and
- // external VideoCaptureModule to use.
- WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine,
- webrtc::VideoCaptureModule* capture);
// For testing purposes. Allows the WebRtcVoiceEngine and
// ViEWrapper to be mocks.
WebRtcVideoEngine(WebRtcVoiceEngine* voice_engine, ViEWrapper* vie_wrapper);
@@ -90,6 +91,12 @@
int GetLastEngineError();
+ // Set the VoiceEngine for A/V sync. This can only be called before Init.
+ bool SetVoiceEngine(WebRtcVoiceEngine* voice_engine);
+
+ // Enable the render module with timing control.
+ bool EnableTimedRender();
+
VideoEncoderConfig& default_encoder_config() {
return default_encoder_config_;
}
@@ -138,6 +145,7 @@
VideoEncoderConfig default_encoder_config_;
bool capture_started_;
talk_base::scoped_ptr<WebRtcRenderAdapter> local_renderer_;
+ bool initialized_;
};
class WebRtcVideoMediaChannel : public VideoMediaChannel,
diff --git a/talk/session/phone/webrtcvoiceengine.cc b/talk/session/phone/webrtcvoiceengine.cc
index a53dfc1..561b025 100644
--- a/talk/session/phone/webrtcvoiceengine.cc
+++ b/talk/session/phone/webrtcvoiceengine.cc
@@ -41,6 +41,7 @@
#include "talk/base/helpers.h"
#include "talk/base/logging.h"
#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
#include "talk/session/phone/webrtcvoe.h"
#ifdef WIN32
@@ -76,6 +77,23 @@
static const char kRtpAudioLevelHeaderExtension[] =
"urn:ietf:params:rtp-hdrext:ssrc-audio-level";
+static const char kIsacCodecName[] = "ISAC";
+static const char kL16CodecName[] = "L16";
+
+// Dumps an AudioCodec in RFC 2327-ish format.
+static std::string ToString(const AudioCodec& codec) {
+ std::stringstream ss;
+ ss << codec.name << "/" << codec.clockrate << "/" << codec.channels
+ << " (" << codec.id << ")";
+ return ss.str();
+}
+static std::string ToString(const webrtc::CodecInst& codec) {
+ std::stringstream ss;
+ ss << codec.plname << "/" << codec.plfreq << "/" << codec.channels
+ << " (" << codec.pltype << ")";
+ return ss.str();
+}
+
static void LogMultiline(talk_base::LoggingSeverity sev, char* text) {
const char* delim = "\r\n";
for (char* tok = strtok(text, delim); tok; tok = strtok(NULL, delim)) {
@@ -83,23 +101,21 @@
}
}
-static const char kL16CodecName[] = "L16";
-
// WebRtcVoiceEngine
const WebRtcVoiceEngine::CodecPref WebRtcVoiceEngine::kCodecPrefs[] = {
- { "ISAC", 16000 },
- { "ISAC", 32000 },
- { "speex", 16000 },
- { "G722", 16000 },
- { "iLBC", 8000 },
- { "speex", 8000 },
- { "PCMU", 8000 },
- { "PCMA", 8000 },
- { "CN", 32000 },
- { "CN", 16000 },
- { "CN", 8000 },
- { "red", 8000 },
- { "telephone-event", 8000 },
+ { "ISAC", 16000, 103 },
+ { "ISAC", 32000, 104 },
+ { "speex", 16000, 107 },
+ { "G722", 16000, 9 },
+ { "ILBC", 8000, 102 },
+ { "speex", 8000, 108 },
+ { "PCMU", 8000, 0 },
+ { "PCMA", 8000, 8 },
+ { "CN", 32000, 106 },
+ { "CN", 16000, 105 },
+ { "CN", 8000, 13 },
+ { "red", 8000, 127 },
+ { "telephone-event", 8000, 126 },
};
class WebRtcSoundclipMedia : public SoundclipMedia {
@@ -191,19 +207,6 @@
Construct();
}
-WebRtcVoiceEngine::WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
- webrtc::AudioDeviceModule* adm_sc)
- : voe_wrapper_(new VoEWrapper()),
- voe_wrapper_sc_(new VoEWrapper()),
- tracing_(new VoETraceWrapper()),
- adm_(adm),
- adm_sc_(adm_sc),
- log_level_(kDefaultLogSeverity),
- is_dumping_aec_(false),
- desired_local_monitor_enable_(false) {
- Construct();
-}
-
WebRtcVoiceEngine::WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
VoEWrapper* voe_wrapper_sc,
VoETraceWrapper* tracing)
@@ -231,28 +234,48 @@
// Clear the default agc state.
memset(&default_agc_config_, 0, sizeof(default_agc_config_));
- // Load our audio codec list
+ // Load our audio codec list.
+ ConstructCodecs();
+}
+
+void WebRtcVoiceEngine::ConstructCodecs() {
LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
for (int i = 0; i < ncodecs; ++i) {
- webrtc::CodecInst gcodec;
- if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
- // Skip the codecs that we don't support.
- if (strcmp(gcodec.plname, kL16CodecName) == 0) {
+ webrtc::CodecInst voe_codec;
+ if (voe_wrapper_->codec()->GetCodec(i, voe_codec) >= 0) {
+ // Skip uncompressed formats.
+ if (_stricmp(voe_codec.plname, kL16CodecName) == 0) {
continue;
}
- int pref = GetCodecPreference(gcodec.plname, gcodec.plfreq);
- if (pref != -1) {
- if (gcodec.rate == -1) gcodec.rate = 0;
- AudioCodec codec(gcodec.pltype, gcodec.plname, gcodec.plfreq,
- gcodec.rate, gcodec.channels, pref);
- LOG(LS_INFO) << gcodec.plname << "/" << gcodec.plfreq << "/" \
- << gcodec.channels << " " << gcodec.pltype;
+
+ const CodecPref* pref = NULL;
+ for (size_t j = 0; j < ARRAY_SIZE(kCodecPrefs); ++j) {
+ if (_stricmp(kCodecPrefs[j].name, voe_codec.plname) == 0 &&
+ kCodecPrefs[j].clockrate == voe_codec.plfreq) {
+ pref = &kCodecPrefs[j];
+ break;
+ }
+ }
+
+ if (pref) {
+ // Use the payload type that we've configured in our pref table;
+ // use the offset in our pref table to determine the sort order.
+ AudioCodec codec(pref->payload_type, voe_codec.plname, voe_codec.plfreq,
+ voe_codec.rate, voe_codec.channels,
+ ARRAY_SIZE(kCodecPrefs) - (pref - kCodecPrefs));
+ LOG(LS_INFO) << ToString(codec);
+ // For ISAC, use 0 to indicate auto bandwidth in our signaling.
+ if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+ codec.bitrate = 0;
+ }
codecs_.push_back(codec);
+ } else {
+ LOG(LS_WARNING) << "Unexpected codec: " << ToString(voe_codec);
}
}
}
- // Make sure they are in local preference order
+ // Make sure they are in local preference order.
std::sort(codecs_.begin(), codecs_.end(), &AudioCodec::Preferable);
}
@@ -340,8 +363,7 @@
LOG(LS_INFO) << "WebRtc VoiceEngine codecs:";
for (std::vector<AudioCodec>::const_iterator it = codecs_.begin();
it != codecs_.end(); ++it) {
- LOG(LS_INFO) << it->name << "/" << it->clockrate << "/"
- << it->channels << " " << it->id;
+ LOG(LS_INFO) << ToString(*it);
}
#if defined(LINUX) && !defined(HAVE_LIBPULSE)
@@ -701,21 +723,29 @@
return FindWebRtcCodec(in, NULL);
}
+// Get the VoiceEngine codec that matches |in|, with the supplied settings.
bool WebRtcVoiceEngine::FindWebRtcCodec(const AudioCodec& in,
webrtc::CodecInst* out) {
int ncodecs = voe_wrapper_->codec()->NumOfCodecs();
for (int i = 0; i < ncodecs; ++i) {
- webrtc::CodecInst gcodec;
- if (voe_wrapper_->codec()->GetCodec(i, gcodec) >= 0) {
- AudioCodec codec(gcodec.pltype, gcodec.plname,
- gcodec.plfreq, gcodec.rate, gcodec.channels, 0);
+ webrtc::CodecInst voe_codec;
+ if (voe_wrapper_->codec()->GetCodec(i, voe_codec) >= 0) {
+ AudioCodec codec(voe_codec.pltype, voe_codec.plname, voe_codec.plfreq,
+ voe_codec.rate, voe_codec.channels, 0);
+ // Allow arbitrary rates for ISAC to be specified.
+ if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+ codec.bitrate = 0;
+ }
if (codec.Matches(in)) {
if (out) {
- // If the codec is VBR and an explicit rate is specified, use it.
- if (in.bitrate != 0 && gcodec.rate == -1) {
- gcodec.rate = in.bitrate;
+ // Fixup the payload type.
+ voe_codec.pltype = in.id;
+ // If ISAC is being used, and an explicit bitrate is not specified,
+ // enable auto bandwidth adjustment.
+ if (_stricmp(codec.name.c_str(), kIsacCodecName) == 0) {
+ voe_codec.rate = (in.bitrate > 0) ? in.bitrate : -1;
}
- *out = gcodec;
+ *out = voe_codec;
}
return true;
}
@@ -863,16 +893,6 @@
}
}
-int WebRtcVoiceEngine::GetCodecPreference(const char *name, int clockrate) {
- for (size_t i = 0; i < ARRAY_SIZE(kCodecPrefs); ++i) {
- if ((strcmp(kCodecPrefs[i].name, name) == 0) &&
- (kCodecPrefs[i].clockrate == clockrate))
- return ARRAY_SIZE(kCodecPrefs) - i;
- }
- LOG(LS_WARNING) << "Unexpected codec \"" << name << "/" << clockrate << "\"";
- return -1;
-}
-
bool WebRtcVoiceEngine::FindChannelAndSsrc(
int channel_num, WebRtcVoiceMediaChannel** channel, uint32* ssrc) const {
ASSERT(channel != NULL && ssrc != NULL);
@@ -973,6 +993,17 @@
return true;
}
+bool WebRtcVoiceEngine::SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+ webrtc::AudioDeviceModule* adm_sc) {
+ if (initialized_) {
+ LOG(LS_WARNING) << "SetAudioDeviceModule can not be called after Init.";
+ return false;
+ }
+ adm_ = adm;
+ adm_sc_ = adm_sc;
+ return true;
+}
+
// WebRtcVoiceMediaChannel
WebRtcVoiceMediaChannel::WebRtcVoiceMediaChannel(WebRtcVoiceEngine *engine)
: WebRtcMediaChannel<VoiceMediaChannel, WebRtcVoiceEngine>(
@@ -1000,6 +1031,16 @@
// Create a random but nonzero send SSRC
SetSendSsrc(talk_base::CreateRandomNonZeroId());
+
+ // Reset all recv codecs; they will be enabled via SetRecvCodecs.
+ int ncodecs = engine->voe()->codec()->NumOfCodecs();
+ for (int i = 0; i < ncodecs; ++i) {
+ webrtc::CodecInst voe_codec;
+ if (engine->voe()->codec()->GetCodec(i, voe_codec) >= 0) {
+ voe_codec.pltype = -1;
+ engine->voe()->codec()->SetRecPayloadType(voe_channel(), voe_codec);
+ }
+ }
}
WebRtcVoiceMediaChannel::~WebRtcVoiceMediaChannel() {
@@ -1042,26 +1083,22 @@
bool WebRtcVoiceMediaChannel::SetRecvCodecs(
const std::vector<AudioCodec>& codecs) {
- // Update our receive payload types to match what we offered. This only is
- // an issue when a different entity (i.e. a server) is generating the offer
- // for us.
+ // Set the payload types to be used for incoming media.
bool ret = true;
- for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
- i != codecs.end() && ret; ++i) {
- webrtc::CodecInst gcodec;
- if (engine()->FindWebRtcCodec(*i, &gcodec)) {
- if (gcodec.pltype != i->id) {
- LOG(LS_INFO) << "Updating payload type for " << gcodec.plname
- << " from " << gcodec.pltype << " to " << i->id;
- gcodec.pltype = i->id;
- if (engine()->voe()->codec()->SetRecPayloadType(
- voe_channel(), gcodec) == -1) {
- LOG_RTCERR1(SetRecPayloadType, voe_channel());
- ret = false;
- }
+ LOG(LS_INFO) << "Setting receive voice codecs:";
+ for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+ it != codecs.end() && ret; ++it) {
+ webrtc::CodecInst voe_codec;
+ if (engine()->FindWebRtcCodec(*it, &voe_codec)) {
+ LOG(LS_INFO) << ToString(*it);
+ voe_codec.pltype = it->id;
+ if (engine()->voe()->codec()->SetRecPayloadType(
+ voe_channel(), voe_codec) == -1) {
+ LOG_RTCERR2(SetRecPayloadType, voe_channel(), ToString(voe_codec));
+ ret = false;
}
} else {
- LOG(LS_WARNING) << "Unknown codec " << i->name;
+ LOG(LS_WARNING) << "Unknown codec " << ToString(*it);
ret = false;
}
}
@@ -1082,29 +1119,30 @@
webrtc::CodecInst send_codec;
memset(&send_codec, 0, sizeof(send_codec));
- for (std::vector<AudioCodec>::const_iterator i = codecs.begin();
- i != codecs.end(); ++i) {
+ for (std::vector<AudioCodec>::const_iterator it = codecs.begin();
+ it != codecs.end(); ++it) {
// Ignore codecs we don't know about. The negotiation step should prevent
// this, but double-check to be sure.
- webrtc::CodecInst gcodec;
- if (!engine()->FindWebRtcCodec(*i, &gcodec)) {
- LOG(LS_WARNING) << "Unknown codec " << i->name;
+ webrtc::CodecInst voe_codec;
+ if (!engine()->FindWebRtcCodec(*it, &voe_codec)) {
+ LOG(LS_WARNING) << "Unknown codec " << ToString(voe_codec);
continue;
}
// Find the DTMF telephone event "codec" and tell VoiceEngine about it.
- if (i->name == "telephone-event" || i->name == "audio/telephone-event") {
+ if (_stricmp(it->name.c_str(), "telephone-event") == 0 ||
+ _stricmp(it->name.c_str(), "audio/telephone-event") == 0) {
engine()->voe()->dtmf()->SetSendTelephoneEventPayloadType(
- voe_channel(), i->id);
+ voe_channel(), it->id);
dtmf_allowed_ = true;
}
// Turn voice activity detection/comfort noise on if supported.
// Set the wideband CN payload type appropriately.
// (narrowband always uses the static payload type 13).
- if (i->name == "CN") {
+ if (_stricmp(it->name.c_str(), "CN") == 0) {
webrtc::PayloadFrequencies cn_freq;
- switch (i->clockrate) {
+ switch (it->clockrate) {
case 8000:
cn_freq = webrtc::kFreq8000Hz;
break;
@@ -1115,14 +1153,14 @@
cn_freq = webrtc::kFreq32000Hz;
break;
default:
- LOG(LS_WARNING) << "CN frequency " << i->clockrate
+ LOG(LS_WARNING) << "CN frequency " << it->clockrate
<< " not supported.";
continue;
}
engine()->voe()->codec()->SetVADStatus(voe_channel(), true);
if (cn_freq != webrtc::kFreq8000Hz) {
engine()->voe()->codec()->SetSendCNPayloadType(voe_channel(),
- i->id, cn_freq);
+ it->id, cn_freq);
}
}
@@ -1131,24 +1169,23 @@
// "red", for FEC audio, is a special case where the actual codec to be
// used is specified in params.
if (first) {
- if (i->name == "red") {
+ if (_stricmp(it->name.c_str(), "red") == 0) {
// Parse out the RED parameters. If we fail, just ignore RED;
// we don't support all possible params/usage scenarios.
- if (!GetRedSendCodec(*i, codecs, &send_codec)) {
+ if (!GetRedSendCodec(*it, codecs, &send_codec)) {
continue;
}
// Enable redundant encoding of the specified codec. Treat any
// failure as a fatal internal error.
- LOG(LS_INFO) << "Enabling RED";
+ LOG(LS_INFO) << "Enabling FEC";
if (engine()->voe()->rtp()->SetFECStatus(voe_channel(),
- true, i->id) == -1) {
- LOG_RTCERR3(SetFECStatus, voe_channel(), true, i->id);
+ true, it->id) == -1) {
+ LOG_RTCERR3(SetFECStatus, voe_channel(), true, it->id);
return false;
}
} else {
- send_codec = gcodec;
- send_codec.pltype = i->id;
+ send_codec = voe_codec;
}
first = false;
}
@@ -1163,11 +1200,11 @@
}
// Set the codec.
- LOG(LS_INFO) << "Selected voice codec " << send_codec.plname
- << "/" << send_codec.plfreq;
+ LOG(LS_INFO) << "Selected voice codec " << ToString(send_codec)
+ << ", bitrate=" << send_codec.rate;
if (engine()->voe()->codec()->SetSendCodec(voe_channel(),
- send_codec) == -1) {
- LOG_RTCERR1(SetSendCodec, voe_channel());
+ send_codec) == -1) {
+ LOG_RTCERR2(SetSendCodec, voe_channel(), ToString(send_codec));
return false;
}
@@ -1893,7 +1930,6 @@
// SetSendCodec, with the desired payload type.
if (codec != all_codecs.end() &&
engine()->FindWebRtcCodec(*codec, send_codec)) {
- send_codec->pltype = red_pt;
} else {
LOG(LS_WARNING) << "RED params " << red_params << " are invalid.";
return false;
diff --git a/talk/session/phone/webrtcvoiceengine.h b/talk/session/phone/webrtcvoiceengine.h
index 8234b9b..2f2ab13 100644
--- a/talk/session/phone/webrtcvoiceengine.h
+++ b/talk/session/phone/webrtcvoiceengine.h
@@ -41,6 +41,11 @@
#include "talk/session/phone/channel.h"
#include "talk/session/phone/rtputils.h"
#include "talk/session/phone/webrtccommon.h"
+#ifdef WEBRTC_RELATIVE_PATH
+#include "voice_engine/main/interface/voe_base.h"
+#else
+#include "third_party/webrtc/files/include/voe_base.h"
+#endif // WEBRTC_RELATIVE_PATH
namespace cricket {
@@ -81,8 +86,6 @@
public webrtc::TraceCallback {
public:
WebRtcVoiceEngine();
- WebRtcVoiceEngine(webrtc::AudioDeviceModule* adm,
- webrtc::AudioDeviceModule* adm_sc);
// Dependency injection for testing.
WebRtcVoiceEngine(VoEWrapper* voe_wrapper,
VoEWrapper* voe_wrapper_sc,
@@ -131,6 +134,10 @@
VoEWrapper* voe_sc() { return voe_wrapper_sc_.get(); }
int GetLastEngineError();
+ // Set the external ADMs. This can only be called before Init.
+ bool SetAudioDeviceModule(webrtc::AudioDeviceModule* adm,
+ webrtc::AudioDeviceModule* adm_sc);
+
private:
typedef std::vector<WebRtcSoundclipMedia *> SoundclipList;
typedef std::vector<WebRtcVoiceMediaChannel *> ChannelList;
@@ -138,15 +145,16 @@
struct CodecPref {
const char* name;
int clockrate;
+ int payload_type;
};
void Construct();
+ void ConstructCodecs();
bool InitInternal();
void ApplyLogging(const std::string& log_filter);
virtual void Print(const webrtc::TraceLevel level,
const char* trace_string, const int length);
virtual void CallbackOnError(const int channel, const int errCode);
- static int GetCodecPreference(const char *name, int clockrate);
// Given the device type, name, and id, find device id. Return true and
// set the output parameter rtc_id if successful.
bool FindWebRtcAudioDeviceId(
diff --git a/talk/session/phone/webrtcvoiceengine_unittest.cc b/talk/session/phone/webrtcvoiceengine_unittest.cc
index 0660630..768f1fd 100644
--- a/talk/session/phone/webrtcvoiceengine_unittest.cc
+++ b/talk/session/phone/webrtcvoiceengine_unittest.cc
@@ -14,7 +14,7 @@
// Tests for the WebRtcVoiceEngine/VoiceChannel code.
static const cricket::AudioCodec kPcmuCodec(0, "PCMU", 8000, 64000, 1, 0);
-static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, -1, 1, 0);
+static const cricket::AudioCodec kIsacCodec(103, "ISAC", 16000, 32000, 1, 0);
static const cricket::AudioCodec kRedCodec(117, "red", 8000, 0, 1, 0);
static const cricket::AudioCodec kCn8000Codec(13, "CN", 8000, 0, 1, 0);
static const cricket::AudioCodec kCn16000Codec(105, "CN", 16000, 0, 1, 0);
@@ -87,7 +87,7 @@
uint32 ssrc_;
cricket::VoiceMediaChannel::Error error_;
};
- // TODO: Implement other stub interfaces (VQE)
+
WebRtcVoiceEngineTest()
: voe_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
voe_sc_(kAudioCodecs, ARRAY_SIZE(kAudioCodecs)),
@@ -149,6 +149,21 @@
EXPECT_TRUE(channel_ == NULL);
}
+// Tests that the list of supported codecs is created properly and ordered
+// correctly
+TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
+ const std::vector<cricket::AudioCodec>& codecs = engine_.codecs();
+ ASSERT_FALSE(codecs.empty());
+ EXPECT_EQ("ISAC", codecs[0].name);
+ EXPECT_EQ(16000, codecs[0].clockrate);
+ EXPECT_EQ(0, codecs[0].bitrate);
+ int pref = codecs[0].preference;
+ for (size_t i = 1; i < codecs.size(); ++i) {
+ EXPECT_GT(pref, codecs[i].preference);
+ pref = codecs[i].preference;
+ }
+}
+
// Tests that we can find codecs by name or id, and that we interpret the
// clockrate and bitrate fields properly.
TEST_F(WebRtcVoiceEngineTest, FindCodec) {
@@ -164,20 +179,24 @@
codec = kIsacCodec;
codec.id = 127;
EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(codec.id, codec_inst.pltype);
// Find PCMU with a 0 clockrate.
codec = kPcmuCodec;
codec.clockrate = 0;
EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(codec.id, codec_inst.pltype);
EXPECT_EQ(8000, codec_inst.plfreq);
// Find PCMU with a 0 bitrate.
codec = kPcmuCodec;
codec.bitrate = 0;
EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(codec.id, codec_inst.pltype);
EXPECT_EQ(64000, codec_inst.rate);
// Find ISAC with an explicit bitrate.
codec = kIsacCodec;
codec.bitrate = 32000;
EXPECT_TRUE(engine_.FindWebRtcCodec(codec, &codec_inst));
+ EXPECT_EQ(codec.id, codec_inst.pltype);
EXPECT_EQ(32000, codec_inst.rate);
}
@@ -188,14 +207,22 @@
std::vector<cricket::AudioCodec> codecs;
codecs.push_back(kIsacCodec);
codecs.push_back(kPcmuCodec);
- codecs[0].id = 96;
+ codecs.push_back(kTelephoneEventCodec);
+ codecs[0].id = 106; // collide with existing telephone-event
+ codecs[2].id = 126;
EXPECT_TRUE(channel_->SetRecvCodecs(codecs));
webrtc::CodecInst gcodec;
talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname), "ISAC");
gcodec.plfreq = 16000;
EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
- EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_EQ(106, gcodec.pltype);
EXPECT_STREQ("ISAC", gcodec.plname);
+ talk_base::strcpyn(gcodec.plname, ARRAY_SIZE(gcodec.plname),
+ "telephone-event");
+ gcodec.plfreq = 8000;
+ EXPECT_EQ(0, voe_.GetRecPayloadType(channel_num, gcodec));
+ EXPECT_EQ(126, gcodec.pltype);
+ EXPECT_STREQ("telephone-event", gcodec.plname);
}
// Test that we fail to set an unknown inbound codec.
@@ -207,6 +234,16 @@
EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
}
+// Test that we fail if we have duplicate types in the inbound list.
+TEST_F(WebRtcVoiceEngineTest, SetRecvCodecsDuplicatePayloadType) {
+ EXPECT_TRUE(SetupEngine());
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kCn16000Codec);
+ codecs[1].id = kIsacCodec.id;
+ EXPECT_FALSE(channel_->SetRecvCodecs(codecs));
+}
+
// Test that we apply codecs properly.
TEST_F(WebRtcVoiceEngineTest, SetSendCodecs) {
EXPECT_TRUE(SetupEngine());
@@ -228,6 +265,44 @@
EXPECT_EQ(106, voe_.GetSendTelephoneEventPayloadType(channel_num));
}
+// Test that we handle various ways of specifying bitrate.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsBitrate) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec); // bitrate == 32000
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(103, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_EQ(32000, gcodec.rate);
+ codecs[0].bitrate = 0; // bitrate == default
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(103, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_EQ(-1, gcodec.rate);
+ codecs[0].bitrate = 28000; // bitrate == 28000
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(103, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_EQ(28000, gcodec.rate);
+ codecs[0] = kPcmuCodec; // bitrate == 64000
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.pltype);
+ EXPECT_STREQ("PCMU", gcodec.plname);
+ EXPECT_EQ(64000, gcodec.rate);
+ codecs[0].bitrate = 0; // bitrate == default
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(0, gcodec.pltype);
+ EXPECT_STREQ("PCMU", gcodec.plname);
+ EXPECT_EQ(64000, gcodec.rate);
+}
+
// Test that we fall back to PCMU if no codecs are specified.
TEST_F(WebRtcVoiceEngineTest, SetSendCodecsNoCodecs) {
EXPECT_TRUE(SetupEngine());
@@ -272,6 +347,33 @@
EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
}
+// Test that we perform case-insensitive matching of codec names.
+TEST_F(WebRtcVoiceEngineTest, SetSendCodecsCaseInsensitive) {
+ EXPECT_TRUE(SetupEngine());
+ int channel_num = voe_.GetLastChannel();
+ std::vector<cricket::AudioCodec> codecs;
+ codecs.push_back(kIsacCodec);
+ codecs.push_back(kPcmuCodec);
+ codecs.push_back(kCn16000Codec);
+ codecs.push_back(kCn8000Codec);
+ codecs.push_back(kTelephoneEventCodec);
+ codecs.push_back(kRedCodec);
+ codecs[0].name = "iSaC";
+ codecs[0].id = 96;
+ codecs[2].id = 97; // wideband CN
+ codecs[4].id = 98; // DTMF
+ EXPECT_TRUE(channel_->SetSendCodecs(codecs));
+ webrtc::CodecInst gcodec;
+ EXPECT_EQ(0, voe_.GetSendCodec(channel_num, gcodec));
+ EXPECT_EQ(96, gcodec.pltype);
+ EXPECT_STREQ("ISAC", gcodec.plname);
+ EXPECT_TRUE(voe_.GetVAD(channel_num));
+ EXPECT_FALSE(voe_.GetFEC(channel_num));
+ EXPECT_EQ(13, voe_.GetSendCNPayloadType(channel_num, false));
+ EXPECT_EQ(97, voe_.GetSendCNPayloadType(channel_num, true));
+ EXPECT_EQ(98, voe_.GetSendTelephoneEventPayloadType(channel_num));
+}
+
// Test that we set up FEC correctly.
TEST_F(WebRtcVoiceEngineTest, SetSendCodecsRED) {
EXPECT_TRUE(SetupEngine());
@@ -1014,21 +1116,21 @@
cricket::AudioCodec(96, "ISAC", 16000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "ISAC", 32000, 0, 1, 0)));
+ // Check that name matching is case-insensitive.
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "ILBC", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "iLBC", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "PCMU", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "PCMA", 8000, 0, 1, 0)));
-// TODO: Add speex back, once webrtc supports it.
-// EXPECT_TRUE(engine.FindCodec(
-// cricket::AudioCodec(96, "speex", 16000, 0, 1, 0)));
-// EXPECT_TRUE(engine.FindCodec(
-// cricket::AudioCodec(96, "speex", 8000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "speex", 16000, 0, 1, 0)));
+ EXPECT_TRUE(engine.FindCodec(
+ cricket::AudioCodec(96, "speex", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "G722", 16000, 0, 1, 0)));
-// EXPECT_TRUE(engine.FindCodec(
-// cricket::AudioCodec(96, "GSM", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(96, "red", 8000, 0, 1, 0)));
EXPECT_TRUE(engine.FindCodec(
@@ -1045,7 +1147,7 @@
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(8, "", 8000, 0, 1, 0))); // PCMA
EXPECT_TRUE(engine.FindCodec(
- cricket::AudioCodec(9, "", 16000, 0, 1, 0))); // G722
+ cricket::AudioCodec(9, "", 16000, 0, 1, 0))); // G722
EXPECT_TRUE(engine.FindCodec(
cricket::AudioCodec(13, "", 8000, 0, 1, 0))); // CN
// Check sample/bitrate matching.
@@ -1058,43 +1160,31 @@
EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 5000, 0, 1, 0)));
EXPECT_FALSE(engine.FindCodec(cricket::AudioCodec(0, "", 0, 5000, 1, 0)));
// Check that there aren't any extra codecs lying around.
- EXPECT_EQ(11U, engine.codecs().size());
+ EXPECT_EQ(13U, engine.codecs().size());
// Verify the payload id of common audio codecs, including CN, ISAC, and G722.
- // TODO: WebRtc team may change the payload id.
for (std::vector<cricket::AudioCodec>::const_iterator it =
engine.codecs().begin(); it != engine.codecs().end(); ++it) {
if (it->name == "CN" && it->clockrate == 16000) {
- EXPECT_EQ(98, it->id);
+ EXPECT_EQ(105, it->id);
} else if (it->name == "CN" && it->clockrate == 32000) {
- EXPECT_EQ(99, it->id);
+ EXPECT_EQ(106, it->id);
} else if (it->name == "ISAC" && it->clockrate == 16000) {
EXPECT_EQ(103, it->id);
} else if (it->name == "ISAC" && it->clockrate == 32000) {
EXPECT_EQ(104, it->id);
} else if (it->name == "G722" && it->clockrate == 16000) {
EXPECT_EQ(9, it->id);
+ } else if (it->name == "telephone-event") {
+ EXPECT_EQ(126, it->id);
+ } else if (it->name == "red") {
+ EXPECT_EQ(127, it->id);
}
}
engine.Terminate();
}
-// Tests that the list of supported codecs is created properly and ordered
-// correctly
-TEST_F(WebRtcVoiceEngineTest, CodecPreference) {
- cricket::WebRtcVoiceEngine engine;
- const std::vector<cricket::AudioCodec>& codecs = engine.codecs();
- ASSERT_FALSE(codecs.empty());
- EXPECT_EQ("ISAC", codecs[0].name);
- EXPECT_EQ(16000, codecs[0].clockrate);
- EXPECT_EQ(32000, codecs[0].bitrate);
- int pref = codecs[0].preference;
- for (size_t i = 1; i < codecs.size(); ++i) {
- EXPECT_GT(pref, codecs[i].preference);
- pref = codecs[i].preference;
- }
-}
-
+// Tests that VoE supports at least 32 channels
TEST(WebRtcVoiceEngineLibTest, Has32Channels) {
cricket::WebRtcVoiceEngine engine;
EXPECT_TRUE(engine.Init());
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
index cf63e79..1003f39 100644
--- a/talk/xmpp/constants.cc
+++ b/talk/xmpp/constants.cc
@@ -367,6 +367,9 @@
const QName QN_PUBSUB(true, NS_PUBSUB, "pubsub");
const QName QN_PUBSUB_ITEMS(true, NS_PUBSUB, "items");
const QName QN_PUBSUB_ITEM(true, NS_PUBSUB, "item");
+const QName QN_PUBSUB_PUBLISH(true, NS_PUBSUB, "publish");
+const QName QN_PUBSUB_RETRACT(true, NS_PUBSUB, "retract");
+const QName QN_ATTR_PUBLISHER(true, STR_EMPTY, "publisher");
const std::string NS_PUBSUB_EVENT("http://jabber.org/protocol/pubsub#event");
const QName QN_NODE(true, STR_EMPTY, "node");
@@ -374,9 +377,19 @@
const QName QN_PUBSUB_EVENT_ITEMS(true, NS_PUBSUB_EVENT, "items");
const QName QN_PUBSUB_EVENT_ITEM(true, NS_PUBSUB_EVENT, "item");
const QName QN_PUBSUB_EVENT_RETRACT(true, NS_PUBSUB_EVENT, "retract");
+const QName QN_NOTIFY(true, STR_EMPTY, "notify");
+const std::string NS_PRESENTER("google:presenter");
+const QName QN_PRESENTER_PRESENTER(true, NS_PRESENTER, "presenter");
+const QName QN_PRESENTER_PRESENTATION_ITEM(
+ true, NS_PRESENTER, "presentation-item");
+const QName QN_PRESENTER_PRESENTATION_TYPE(
+ true, NS_PRESENTER, "presentation-type");
+const QName QN_PRESENTER_PRESENTATION_ID(true, NS_PRESENTER, "presentation-id");
+
+
// JEP 0030
const QName QN_CATEGORY(true, STR_EMPTY, "category");
@@ -567,4 +580,13 @@
const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
const QName QN_LABEL(true, STR_EMPTY, "label");
+const std::string NS_GOOGLE_MUC_MEDIA("google:muc#media");
+const QName QN_GOOGLE_MUC_AUDIO_MUTE(
+ true, NS_GOOGLE_MUC_MEDIA, "audio-mute");
+const QName QN_GOOGLE_MUC_VIDEO_MUTE(
+ true, NS_GOOGLE_MUC_MEDIA, "video-mute");
+const QName QN_GOOGLE_MUC_RECORDING(
+ true, NS_GOOGLE_MUC_MEDIA, "recording");
+const QName QN_STATE_ATTR(true, STR_EMPTY, "state");
+
}
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
index ba9c9e1..4409283 100644
--- a/talk/xmpp/constants.h
+++ b/talk/xmpp/constants.h
@@ -331,6 +331,9 @@
extern const QName QN_PUBSUB;
extern const QName QN_PUBSUB_ITEMS;
extern const QName QN_PUBSUB_ITEM;
+extern const QName QN_PUBSUB_PUBLISH;
+extern const QName QN_PUBSUB_RETRACT;
+extern const QName QN_ATTR_PUBLISHER;
extern const std::string NS_PUBSUB_EVENT;
extern const QName QN_NODE;
@@ -338,6 +341,14 @@
extern const QName QN_PUBSUB_EVENT_ITEMS;
extern const QName QN_PUBSUB_EVENT_ITEM;
extern const QName QN_PUBSUB_EVENT_RETRACT;
+extern const QName QN_NOTIFY;
+
+
+extern const std::string NS_PRESENTER;
+extern const QName QN_PRESENTER_PRESENTER;
+extern const QName QN_PRESENTER_PRESENTATION_ITEM;
+extern const QName QN_PRESENTER_PRESENTATION_TYPE;
+extern const QName QN_PRESENTER_PRESENTATION_ID;
@@ -510,6 +521,12 @@
extern const QName QN_GOOGLE_MUC_USER_STATUS;
extern const QName QN_LABEL;
+extern const std::string NS_GOOGLE_MUC_MEDIA;
+extern const QName QN_GOOGLE_MUC_AUDIO_MUTE;
+extern const QName QN_GOOGLE_MUC_VIDEO_MUTE;
+extern const QName QN_GOOGLE_MUC_RECORDING;
+extern const QName QN_STATE_ATTR;
+
} // namespace buzz
#endif // TALK_XMPP_CONSTANTS_H_
diff --git a/talk/xmpp/hangoutpubsubclient.cc b/talk/xmpp/hangoutpubsubclient.cc
new file mode 100644
index 0000000..ea5534d
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.cc
@@ -0,0 +1,466 @@
+/*
+ * libjingle
+ * Copyright 2011, 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 "talk/xmpp/hangoutpubsubclient.h"
+
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+namespace {
+const std::string kPresenting = "s";
+const std::string kNotPresenting = "o";
+} // namespace
+
+
+// Knows how to handle specific states and XML.
+template <typename C>
+class PubSubStateSerializer {
+ public:
+ virtual XmlElement* Write(const QName& state_name, const C& state) = 0;
+ virtual C Parse(const XmlElement* state_elem) = 0;
+};
+
+// A simple serialiazer where presence of item => true, lack of item
+// => false.
+class BoolStateSerializer : public PubSubStateSerializer<bool> {
+ virtual XmlElement* Write(const QName& state_name, const bool& state) {
+ if (!state) {
+ return NULL;
+ }
+
+ return new XmlElement(state_name, true);
+ }
+
+ virtual bool Parse(const XmlElement* state_elem) {
+ return state_elem != NULL;
+ }
+};
+
+// Adapts PubSubClient to be specifically suited for pub sub call
+// states. Signals state changes and keeps track of keys, which are
+// normally nicks.
+// TODO: Expose this as a generally useful class, not just
+// private to hangouts.
+template <typename C>
+class PubSubStateClient : public sigslot::has_slots<> {
+ public:
+ // Gets ownership of the serializer, but not the client.
+ PubSubStateClient(PubSubClient* client,
+ const QName& state_name,
+ C default_state,
+ PubSubStateSerializer<C>* serializer)
+ : client_(client),
+ state_name_(state_name),
+ default_state_(default_state) {
+ serializer_.reset(serializer);
+ client_->SignalItems.connect(
+ this, &PubSubStateClient<C>::OnItems);
+ client_->SignalPublishResult.connect(
+ this, &PubSubStateClient<C>::OnPublishResult);
+ client_->SignalPublishError.connect(
+ this, &PubSubStateClient<C>::OnPublishError);
+ client_->SignalRetractResult.connect(
+ this, &PubSubStateClient<C>::OnRetractResult);
+ client_->SignalRetractError.connect(
+ this, &PubSubStateClient<C>::OnRetractError);
+ }
+
+ virtual ~PubSubStateClient() {}
+
+ virtual void Publish(const std::string& key, const C& state,
+ std::string* task_id_out) {
+ const std::string& nick = key;
+
+ std::string itemid = state_name_.LocalPart() + ":" + nick;
+ if (StatesEqual(state, default_state_)) {
+ client_->RetractItem(itemid, task_id_out);
+ } else {
+ XmlElement* state_elem = serializer_->Write(state_name_, state);
+ state_elem->AddAttr(QN_NICK, nick);
+ client_->PublishItem(itemid, state_elem, task_id_out);
+ }
+ };
+
+ sigslot::signal1<const PubSubStateChange<C>&> SignalStateChange;
+ // Signal (task_id, item). item is NULL for retract.
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishResult;
+ // Signal (task_id, item, error stanza). item is NULL for retract.
+ sigslot::signal3<const std::string&,
+ const XmlElement*,
+ const XmlElement*> SignalPublishError;
+
+ protected:
+ // return false if retracted item (no state given)
+ virtual bool ParseState(const PubSubItem& item,
+ std::string* key_out,
+ bool* state_out) {
+ const XmlElement* state_elem = item.elem->FirstNamed(state_name_);
+ if (state_elem == NULL) {
+ return false;
+ }
+
+ *key_out = state_elem->Attr(QN_NICK);
+ *state_out = serializer_->Parse(state_elem);
+ return true;
+ };
+
+ virtual bool StatesEqual(C state1, C state2) {
+ return state1 == state2;
+ }
+
+ PubSubClient* client() { return client_; }
+
+ private:
+ void OnItems(PubSubClient* pub_sub_client,
+ const std::vector<PubSubItem>& items) {
+ for (std::vector<PubSubItem>::const_iterator item = items.begin();
+ item != items.end(); ++item) {
+ OnItem(*item);
+ }
+ }
+
+ void OnItem(const PubSubItem& item) {
+ const std::string& itemid = item.itemid;
+
+ std::string key;
+ C new_state;
+ bool retracted = !ParseState(item, &key, &new_state);
+ if (retracted) {
+ bool known_itemid = (key_by_itemid_.find(itemid) != key_by_itemid_.end());
+ if (!known_itemid) {
+ // Nothing to retract, and nothing to publish.
+ // Probably a different state type.
+ return;
+ } else {
+ key = key_by_itemid_[itemid];
+ key_by_itemid_.erase(itemid);
+ new_state = default_state_;
+ }
+ } else {
+ // TODO: Assert parsed key matches the known key when
+ // not retracted. It shouldn't change!
+ key_by_itemid_[itemid] = key;
+ }
+
+ bool has_old_state = (state_by_key_.find(key) != state_by_key_.end());
+ const C& old_state = has_old_state ? state_by_key_[key] : default_state_;
+ if ((retracted && !has_old_state) || StatesEqual(new_state, old_state)) {
+ // Nothing change, so don't bother signalling.
+ return;
+ }
+
+ if (retracted || StatesEqual(new_state, default_state_)) {
+ // We treat a default state similar to a retract.
+ state_by_key_.erase(key);
+ } else {
+ state_by_key_[key] = new_state;
+ }
+
+ PubSubStateChange<C> change;
+ change.key = key;
+ change.old_state = old_state;
+ change.new_state = new_state;
+ change.publisher_nick =
+ Jid(item.elem->Attr(QN_ATTR_PUBLISHER)).resource();
+ SignalStateChange(change);
+ }
+
+ void OnPublishResult(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const XmlElement* item) {
+ SignalPublishResult(task_id, item);
+ }
+
+ void OnPublishError(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const buzz::XmlElement* item,
+ const buzz::XmlElement* stanza) {
+ SignalPublishError(task_id, item, stanza);
+ }
+
+ void OnRetractResult(PubSubClient* pub_sub_client,
+ const std::string& task_id) {
+ // There's no point in differentiating between publish and retract
+ // errors, so we simplify by making them both signal a publish
+ // result.
+ const XmlElement* item = NULL;
+ SignalPublishResult(task_id, item);
+ }
+
+ void OnRetractError(PubSubClient* pub_sub_client,
+ const std::string& task_id,
+ const buzz::XmlElement* stanza) {
+ // There's no point in differentiating between publish and retract
+ // errors, so we simplify by making them both signal a publish
+ // error.
+ const XmlElement* item = NULL;
+ SignalPublishError(task_id, item, stanza);
+ }
+
+ PubSubClient* client_;
+ const QName& state_name_;
+ C default_state_;
+ talk_base::scoped_ptr<PubSubStateSerializer<C> > serializer_;
+ // key => state
+ std::map<std::string, C> state_by_key_;
+ // itemid => key
+ std::map<std::string, std::string> key_by_itemid_;
+};
+
+class PresenterStateClient : public PubSubStateClient<bool> {
+ public:
+ PresenterStateClient(PubSubClient* client,
+ const QName& state_name,
+ bool default_state,
+ PubSubStateSerializer<bool>* serializer = NULL)
+ : PubSubStateClient<bool>(client, state_name, default_state, serializer) {
+ }
+
+ virtual void Publish(const std::string& key, const bool& state,
+ std::string* task_id_out) {
+ const std::string& nick = key;
+
+ XmlElement* presenter_elem = new XmlElement(QN_PRESENTER_PRESENTER, true);
+ // There's a dummy value, not used, but required.
+ presenter_elem->AddAttr(QN_JID, "dummy@value.net");
+ presenter_elem->AddAttr(QN_NICK, nick);
+
+ XmlElement* presentation_item_elem =
+ new XmlElement(QN_PRESENTER_PRESENTATION_ITEM, false);
+ const std::string& presentation_type = state ? kPresenting : kNotPresenting;
+ presentation_item_elem->AddAttr(
+ QN_PRESENTER_PRESENTATION_TYPE, presentation_type);
+
+ // The Presenter state is kind of dumb in that it doesn't use
+ // retracts. It relies on setting the "type" to a special value.
+ std::string itemid = nick;
+ std::vector<XmlElement*> children;
+ children.push_back(presenter_elem);
+ children.push_back(presentation_item_elem);
+ client()->PublishItem(itemid, children, task_id_out);
+ }
+
+ protected:
+ virtual bool ParseState(const PubSubItem& item,
+ std::string* key_out,
+ bool* state_out) {
+ const XmlElement* presenter_elem =
+ item.elem->FirstNamed(QN_PRESENTER_PRESENTER);
+ const XmlElement* presentation_item_elem =
+ item.elem->FirstNamed(QN_PRESENTER_PRESENTATION_ITEM);
+ if (presentation_item_elem == NULL || presenter_elem == NULL) {
+ return false;
+ }
+
+ *key_out = presenter_elem->Attr(QN_NICK);
+ *state_out = (presentation_item_elem->Attr(
+ QN_PRESENTER_PRESENTATION_TYPE) != kNotPresenting);
+ return true;
+ }
+};
+
+HangoutPubSubClient::HangoutPubSubClient(XmppTaskParentInterface* parent,
+ const Jid& mucjid,
+ const std::string& nick)
+ : mucjid_(mucjid),
+ nick_(nick) {
+ presenter_client_.reset(new PubSubClient(parent, mucjid, NS_PRESENTER));
+ presenter_client_->SignalRequestError.connect(
+ this, &HangoutPubSubClient::OnPresenterRequestError);
+
+ media_client_.reset(new PubSubClient(parent, mucjid, NS_GOOGLE_MUC_MEDIA));
+ media_client_->SignalRequestError.connect(
+ this, &HangoutPubSubClient::OnMediaRequestError);
+
+ presenter_state_client_.reset(new PresenterStateClient(
+ presenter_client_.get(), QN_PRESENTER_PRESENTER, false));
+ presenter_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnPresenterStateChange);
+ presenter_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnPresenterPublishResult);
+ presenter_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnPresenterPublishError);
+
+ audio_mute_state_client_.reset(new PubSubStateClient<bool>(
+ media_client_.get(), QN_GOOGLE_MUC_AUDIO_MUTE, false,
+ new BoolStateSerializer()));
+ // Can't just repeat because we need to watch for remote mutes.
+ audio_mute_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnAudioMuteStateChange);
+ audio_mute_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnAudioMutePublishResult);
+ audio_mute_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnAudioMutePublishError);
+
+ recording_state_client_.reset(new PubSubStateClient<bool>(
+ media_client_.get(), QN_GOOGLE_MUC_RECORDING, false,
+ new BoolStateSerializer()));
+ recording_state_client_->SignalStateChange.connect(
+ this, &HangoutPubSubClient::OnRecordingStateChange);
+ recording_state_client_->SignalPublishResult.connect(
+ this, &HangoutPubSubClient::OnRecordingPublishResult);
+ recording_state_client_->SignalPublishError.connect(
+ this, &HangoutPubSubClient::OnRecordingPublishError);
+}
+
+HangoutPubSubClient::~HangoutPubSubClient() {
+}
+
+void HangoutPubSubClient::RequestAll() {
+ presenter_client_->RequestItems();
+ media_client_->RequestItems();
+}
+
+void HangoutPubSubClient::OnPresenterRequestError(
+ PubSubClient* client, const XmlElement* stanza) {
+ SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::OnMediaRequestError(
+ PubSubClient* client, const XmlElement* stanza) {
+ SignalRequestError(client->node(), stanza);
+}
+
+void HangoutPubSubClient::PublishPresenterState(
+ bool presenting, std::string* task_id_out) {
+ presenter_state_client_->Publish(nick_, presenting, task_id_out);
+}
+
+void HangoutPubSubClient::PublishAudioMuteState(
+ bool muted, std::string* task_id_out) {
+ audio_mute_state_client_->Publish(nick_, muted, task_id_out);
+}
+
+void HangoutPubSubClient::PublishRecordingState(
+ bool recording, std::string* task_id_out) {
+ recording_state_client_->Publish(nick_, recording, task_id_out);
+}
+
+// Remote mute is accomplished by setting another client's mute state.
+void HangoutPubSubClient::RemoteMute(
+ const std::string& mutee_nick, std::string* task_id_out) {
+ audio_mute_state_client_->Publish(mutee_nick, true, task_id_out);
+}
+
+void HangoutPubSubClient::OnPresenterStateChange(
+ const PubSubStateChange<bool>& change) {
+ const std::string& nick = change.key;
+ SignalPresenterStateChange(nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnPresenterPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishPresenterResult(task_id);
+}
+
+void HangoutPubSubClient::OnPresenterPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishPresenterError(task_id, stanza);
+}
+
+// Since a remote mute is accomplished by another client setting our
+// mute state, if our state changes to muted, we should mute
+// ourselves. Note that we never remote un-mute, though.
+void HangoutPubSubClient::OnAudioMuteStateChange(
+ const PubSubStateChange<bool>& change) {
+ const std::string& nick = change.key;
+
+ bool was_muted = change.old_state;
+ bool is_muted = change.new_state;
+ bool remote_action =
+ !change.publisher_nick.empty() && (change.publisher_nick != nick);
+ if (is_muted && remote_action) {
+ const std::string& mutee_nick = nick;
+ const std::string& muter_nick = change.publisher_nick;
+ bool should_mute_locally = (mutee_nick == nick_);
+ SignalRemoteMute(mutee_nick, muter_nick, should_mute_locally);
+ } else {
+ SignalAudioMuteStateChange(nick, was_muted, is_muted);
+ }
+}
+
+const std::string& GetAudioMuteNickFromItem(const XmlElement* item) {
+ if (item != NULL) {
+ const XmlElement* audio_mute_state =
+ item->FirstNamed(QN_GOOGLE_MUC_AUDIO_MUTE);
+ if (audio_mute_state != NULL) {
+ return audio_mute_state->Attr(QN_NICK);
+ }
+ }
+ return STR_EMPTY;
+}
+
+void HangoutPubSubClient::OnAudioMutePublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+ if (mutee_nick != nick_) {
+ SignalRemoteMuteResult(task_id, mutee_nick);
+ } else {
+ SignalPublishAudioMuteResult(task_id);
+ }
+}
+
+void HangoutPubSubClient::OnAudioMutePublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ const std::string& mutee_nick = GetAudioMuteNickFromItem(item);
+ if (mutee_nick != nick_) {
+ SignalRemoteMuteError(task_id, mutee_nick, stanza);
+ } else {
+ SignalPublishAudioMuteError(task_id, stanza);
+ }
+}
+
+void HangoutPubSubClient::OnRecordingStateChange(
+ const PubSubStateChange<bool>& change) {
+ const std::string& nick = change.key;
+ SignalRecordingStateChange(nick, change.old_state, change.new_state);
+}
+
+void HangoutPubSubClient::OnRecordingPublishResult(
+ const std::string& task_id, const XmlElement* item) {
+ SignalPublishRecordingResult(task_id);
+}
+
+void HangoutPubSubClient::OnRecordingPublishError(
+ const std::string& task_id, const XmlElement* item,
+ const XmlElement* stanza) {
+ SignalPublishRecordingError(task_id, stanza);
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/hangoutpubsubclient.h b/talk/xmpp/hangoutpubsubclient.h
new file mode 100644
index 0000000..e740738
--- /dev/null
+++ b/talk/xmpp/hangoutpubsubclient.h
@@ -0,0 +1,157 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
+#define TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubclient.h"
+
+// Gives a high-level API for MUC call PubSub needs such as
+// presenter state, recording state, mute state, and remote mute.
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// Represents a PubSub state change. Usually, the key is the nick,
+// but not always. It's a per-state-type thing.
+template <typename C>
+struct PubSubStateChange {
+ std::string key;
+ C old_state;
+ C new_state;
+ std::string publisher_nick;
+};
+
+template <typename C> class PubSubStateClient;
+
+// A client tied to a specific MUC jid and local nick. Provides ways
+// to get updates and publish state and events. Must call
+// RequestAll() to start getting updates.
+class HangoutPubSubClient : public sigslot::has_slots<> {
+ public:
+ HangoutPubSubClient(XmppTaskParentInterface* parent,
+ const Jid& mucjid,
+ const std::string& nick);
+ ~HangoutPubSubClient();
+ const Jid& mucjid() const { return mucjid_; }
+ const std::string& nick() const { return nick_; }
+
+ // Requests all of the different states and subscribes for updates.
+ // Responses and updates will be signalled via the various signals.
+ void RequestAll();
+ // Signal (nick, was_presenting, is_presenting)
+ sigslot::signal3<const std::string&, bool, bool> SignalPresenterStateChange;
+ // Signal (nick, was_muted, is_muted)
+ sigslot::signal3<const std::string&, bool, bool> SignalAudioMuteStateChange;
+ // Signal (nick, was_recording, is_recording)
+ sigslot::signal3<const std::string&, bool, bool> SignalRecordingStateChange;
+ // Signal (muter_nick, mutee_nick, should_mute_locally)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ bool> SignalRemoteMute;
+
+ // Signal (node, error stanza)
+ sigslot::signal2<const std::string&, const XmlElement*> SignalRequestError;
+
+ // On each of these, provide a task_id_out to get the task_id, which
+ // can be correlated to the error and result signals.
+ void PublishPresenterState(
+ bool presenting, std::string* task_id_out = NULL);
+ void PublishAudioMuteState(
+ bool muted, std::string* task_id_out = NULL);
+ void PublishRecordingState(
+ bool recording, std::string* task_id_out = NULL);
+ void RemoteMute(
+ const std::string& mutee_nick, std::string* task_id_out = NULL);
+
+ // Signal task_id
+ sigslot::signal1<const std::string&> SignalPublishAudioMuteResult;
+ sigslot::signal1<const std::string&> SignalPublishPresenterResult;
+ sigslot::signal1<const std::string&> SignalPublishRecordingResult;
+ // Signal (task_id, mutee_nick)
+ sigslot::signal2<const std::string&,
+ const std::string&> SignalRemoteMuteResult;
+
+ // Signal (task_id, error stanza)
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishAudioMuteError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishPresenterError;
+ sigslot::signal2<const std::string&,
+ const XmlElement*> SignalPublishRecordingError;
+ // Signal (task_id, mutee_nick, error stanza)
+ sigslot::signal3<const std::string&,
+ const std::string&,
+ const XmlElement*> SignalRemoteMuteError;
+
+ private:
+ void OnPresenterRequestError(PubSubClient* client,
+ const XmlElement* stanza);
+ void OnMediaRequestError(PubSubClient* client,
+ const XmlElement* stanza);
+
+ void OnPresenterStateChange(const PubSubStateChange<bool>& change);
+ void OnPresenterPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnPresenterPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnAudioMuteStateChange(const PubSubStateChange<bool>& change);
+ void OnAudioMutePublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnAudioMutePublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ void OnRecordingStateChange(const PubSubStateChange<bool>& change);
+ void OnRecordingPublishResult(const std::string& task_id,
+ const XmlElement* item);
+ void OnRecordingPublishError(const std::string& task_id,
+ const XmlElement* item,
+ const XmlElement* stanza);
+ Jid mucjid_;
+ std::string nick_;
+ talk_base::scoped_ptr<PubSubClient> media_client_;
+ talk_base::scoped_ptr<PubSubClient> presenter_client_;
+ talk_base::scoped_ptr<PubSubStateClient<bool> > presenter_state_client_;
+ talk_base::scoped_ptr<PubSubStateClient<bool> > audio_mute_state_client_;
+ talk_base::scoped_ptr<PubSubStateClient<bool> > recording_state_client_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_HANGOUTPUBSUBCLIENT_H_
diff --git a/talk/xmpp/iqtask.h b/talk/xmpp/iqtask.h
index 589616b..2228e6f 100644
--- a/talk/xmpp/iqtask.h
+++ b/talk/xmpp/iqtask.h
@@ -35,27 +35,29 @@
namespace buzz {
-class IqTask : public buzz::XmppTask {
+class IqTask : public XmppTask {
public:
IqTask(XmppTaskParentInterface* parent,
- const std::string& verb, const buzz::Jid& to,
- buzz::XmlElement* el);
+ const std::string& verb, const Jid& to,
+ XmlElement* el);
virtual ~IqTask() {}
+ const XmlElement* stanza() const { return stanza_.get(); }
+
sigslot::signal2<IqTask*,
const XmlElement*> SignalError;
protected:
- virtual void HandleResult(const buzz::XmlElement* element) = 0;
+ virtual void HandleResult(const XmlElement* element) = 0;
private:
virtual int ProcessStart();
- virtual bool HandleStanza(const buzz::XmlElement* stanza);
+ virtual bool HandleStanza(const XmlElement* stanza);
virtual int ProcessResponse();
virtual int OnTimeout();
- buzz::Jid to_;
- talk_base::scoped_ptr<buzz::XmlElement> stanza_;
+ Jid to_;
+ talk_base::scoped_ptr<XmlElement> stanza_;
};
} // namespace buzz
diff --git a/talk/xmpp/pubsubclient.cc b/talk/xmpp/pubsubclient.cc
new file mode 100644
index 0000000..8d6d4c4
--- /dev/null
+++ b/talk/xmpp/pubsubclient.cc
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2011, 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 "talk/xmpp/pubsubclient.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+namespace buzz {
+
+void PubSubClient::RequestItems() {
+ PubSubRequestTask* request_task =
+ new PubSubRequestTask(parent_, pubsubjid_, node_);
+ request_task->SignalResult.connect(this, &PubSubClient::OnRequestResult);
+ request_task->SignalError.connect(this, &PubSubClient::OnRequestError);
+
+ PubSubReceiveTask* receive_task =
+ new PubSubReceiveTask(parent_, pubsubjid_, node_);
+ receive_task->SignalUpdate.connect(this, &PubSubClient::OnReceiveUpdate);
+
+ receive_task->Start();
+ request_task->Start();
+}
+
+void PubSubClient::PublishItem(
+ const std::string& itemid, XmlElement* payload, std::string* task_id_out) {
+ std::vector<XmlElement*> children;
+ children.push_back(payload);
+ PublishItem(itemid, children, task_id_out);
+}
+
+void PubSubClient::PublishItem(
+ const std::string& itemid, const std::vector<XmlElement*>& children,
+ std::string* task_id_out) {
+ PubSubPublishTask* publish_task =
+ new PubSubPublishTask(parent_, pubsubjid_, node_, itemid, children);
+ publish_task->SignalError.connect(this, &PubSubClient::OnPublishError);
+ publish_task->SignalResult.connect(this, &PubSubClient::OnPublishResult);
+ publish_task->Start();
+ if (task_id_out) {
+ *task_id_out = publish_task->task_id();
+ }
+}
+
+void PubSubClient::RetractItem(
+ const std::string& itemid, std::string* task_id_out) {
+ PubSubRetractTask* retract_task =
+ new PubSubRetractTask(parent_, pubsubjid_, node_, itemid);
+ retract_task->SignalError.connect(this, &PubSubClient::OnRetractError);
+ retract_task->SignalResult.connect(this, &PubSubClient::OnRetractResult);
+ retract_task->Start();
+ if (task_id_out) {
+ *task_id_out = retract_task->task_id();
+ }
+}
+
+void PubSubClient::OnRequestResult(PubSubRequestTask* task,
+ const std::vector<PubSubItem>& items) {
+ SignalItems(this, items);
+}
+
+void PubSubClient::OnRequestError(IqTask* task,
+ const XmlElement* stanza) {
+ SignalRequestError(this, stanza);
+}
+
+void PubSubClient::OnReceiveUpdate(PubSubReceiveTask* task,
+ const std::vector<PubSubItem>& items) {
+ SignalItems(this, items);
+}
+
+const XmlElement* GetItemFromStanza(const XmlElement* stanza) {
+ if (stanza != NULL) {
+ const XmlElement* pubsub = stanza->FirstNamed(QN_PUBSUB);
+ if (pubsub != NULL) {
+ const XmlElement* publish = pubsub->FirstNamed(QN_PUBSUB_PUBLISH);
+ if (publish != NULL) {
+ return publish->FirstNamed(QN_PUBSUB_ITEM);
+ }
+ }
+ }
+ return NULL;
+}
+
+void PubSubClient::OnPublishResult(PubSubPublishTask* task) {
+ const XmlElement* item = GetItemFromStanza(task->stanza());
+ SignalPublishResult(this, task->task_id(), item);
+}
+
+void PubSubClient::OnPublishError(IqTask* task,
+ const XmlElement* error_stanza) {
+ PubSubPublishTask* publish_task =
+ static_cast<PubSubPublishTask*>(task);
+ const XmlElement* item = GetItemFromStanza(publish_task->stanza());
+ SignalPublishError(this, publish_task->task_id(), item, error_stanza);
+}
+
+void PubSubClient::OnRetractResult(PubSubRetractTask* task) {
+ SignalRetractResult(this, task->task_id());
+}
+
+void PubSubClient::OnRetractError(IqTask* task,
+ const XmlElement* stanza) {
+ PubSubRetractTask* retract_task =
+ static_cast<PubSubRetractTask*>(task);
+ SignalRetractError(this, retract_task->task_id(), stanza);
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/pubsubclient.h b/talk/xmpp/pubsubclient.h
new file mode 100644
index 0000000..099765a
--- /dev/null
+++ b/talk/xmpp/pubsubclient.h
@@ -0,0 +1,125 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_XMPP_PUBSUBCLIENT_H_
+#define TALK_XMPP_PUBSUBCLIENT_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/task.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/pubsubtasks.h"
+
+// Easy to use clients built on top of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+class Jid;
+class XmlElement;
+class XmppTaskParentInterface;
+
+// An easy-to-use pubsub client that handles the three tasks of
+// getting, publishing, and listening for updates. Tied to a specific
+// pubsub jid and node. All you have to do is RequestItems, listen
+// for SignalItems and PublishItems.
+class PubSubClient : public sigslot::has_slots<> {
+ public:
+ PubSubClient(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : parent_(parent),
+ pubsubjid_(pubsubjid),
+ node_(node) {}
+
+ const std::string& node() const { return node_; }
+
+ // Requests the <pubsub><items>, which will be returned via
+ // SignalItems, or SignalRequestError if there is a failure. Should
+ // auto-subscribe.
+ void RequestItems();
+ // Fired when either <pubsub><items> are returned or when
+ // <event><items> are received.
+ sigslot::signal2<PubSubClient*,
+ const std::vector<PubSubItem>&> SignalItems;
+ // Signal (this, error stanza)
+ sigslot::signal2<PubSubClient*,
+ const XmlElement*> SignalRequestError;
+ // Signal (this, task_id, item, error stanza)
+ sigslot::signal4<PubSubClient*,
+ const std::string&,
+ const XmlElement*,
+ const XmlElement*> SignalPublishError;
+ // Signal (this, task_id, item)
+ sigslot::signal3<PubSubClient*,
+ const std::string&,
+ const XmlElement*> SignalPublishResult;
+ // Signal (this, task_id, error stanza)
+ sigslot::signal3<PubSubClient*,
+ const std::string&,
+ const XmlElement*> SignalRetractError;
+ // Signal (this, task_id)
+ sigslot::signal2<PubSubClient*,
+ const std::string&> SignalRetractResult;
+
+ // Publish an item. Takes ownership of payload.
+ void PublishItem(const std::string& itemid,
+ XmlElement* payload,
+ std::string* task_id_out);
+ // Publish an item. Takes ownership of children.
+ void PublishItem(const std::string& itemid,
+ const std::vector<XmlElement*>& children,
+ std::string* task_id_out);
+ // Retract (delete) an item.
+ void RetractItem(const std::string& itemid,
+ std::string* task_id_out);
+
+ private:
+ void OnRequestError(IqTask* task,
+ const XmlElement* stanza);
+ void OnRequestResult(PubSubRequestTask* task,
+ const std::vector<PubSubItem>& items);
+ void OnReceiveUpdate(PubSubReceiveTask* task,
+ const std::vector<PubSubItem>& items);
+ void OnPublishResult(PubSubPublishTask* task);
+ void OnPublishError(IqTask* task,
+ const XmlElement* stanza);
+ void OnRetractResult(PubSubRetractTask* task);
+ void OnRetractError(IqTask* task,
+ const XmlElement* stanza);
+
+ XmppTaskParentInterface* parent_;
+ Jid pubsubjid_;
+ std::string node_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_PUBSUBCLIENT_H_
diff --git a/talk/xmpp/pubsubtasks.cc b/talk/xmpp/pubsubtasks.cc
new file mode 100644
index 0000000..bbefbe5
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.cc
@@ -0,0 +1,214 @@
+/*
+ * libjingle
+ * Copyright 2011, 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 "talk/xmpp/pubsubtasks.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/receivetask.h"
+
+// An implementation of the tasks for XEP-0060
+// (http://xmpp.org/extensions/xep-0060.html).
+
+namespace buzz {
+
+namespace {
+
+bool IsPubSubEventItemsElem(const XmlElement* stanza,
+ const std::string& expected_node) {
+ if (stanza->Name() != QN_MESSAGE) {
+ return false;
+ }
+
+ const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+ if (event_elem == NULL) {
+ return false;
+ }
+
+ const XmlElement* items_elem = event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+ if (items_elem == NULL) {
+ return false;
+ }
+
+ const std::string& actual_node = items_elem->Attr(QN_NODE);
+ return (actual_node == expected_node);
+}
+
+
+// Creates <pubsub node="node"><items></pubsub>
+XmlElement* CreatePubSubItemsElem(const std::string& node) {
+ XmlElement* items_elem = new XmlElement(QN_PUBSUB_ITEMS, false);
+ items_elem->AddAttr(QN_NODE, node);
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, false);
+ pubsub_elem->AddElement(items_elem);
+ return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubPublishItemElem(
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children) {
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+ XmlElement* publish_elem = new XmlElement(QN_PUBSUB_PUBLISH, false);
+ publish_elem->AddAttr(QN_NODE, node);
+ XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+ item_elem->AddAttr(QN_ID, itemid);
+ for (std::vector<XmlElement*>::const_iterator child = children.begin();
+ child != children.end(); ++child) {
+ item_elem->AddElement(*child);
+ }
+ publish_elem->AddElement(item_elem);
+ pubsub_elem->AddElement(publish_elem);
+ return pubsub_elem;
+}
+
+// Creates <pubsub node="node"><publish><item id="itemid">payload</item>...
+// Takes ownership of payload.
+XmlElement* CreatePubSubRetractItemElem(const std::string& node,
+ const std::string& itemid) {
+ XmlElement* pubsub_elem = new XmlElement(QN_PUBSUB, true);
+ XmlElement* retract_elem = new XmlElement(QN_PUBSUB_RETRACT, false);
+ retract_elem->AddAttr(QN_NODE, node);
+ retract_elem->AddAttr(QN_NOTIFY, "true");
+ XmlElement* item_elem = new XmlElement(QN_PUBSUB_ITEM, false);
+ item_elem->AddAttr(QN_ID, itemid);
+ retract_elem->AddElement(item_elem);
+ pubsub_elem->AddElement(retract_elem);
+ return pubsub_elem;
+}
+
+void ParseItem(const XmlElement* item_elem,
+ std::vector<PubSubItem>* items) {
+ PubSubItem item;
+ item.itemid = item_elem->Attr(QN_ID);
+ item.elem = item_elem;
+ items->push_back(item);
+}
+
+// Right now, <retract>s are treated the same as items with empty
+// payloads. We may want to change it in the future, but right now
+// it's sufficient for our needs.
+void ParseRetract(const XmlElement* retract_elem,
+ std::vector<PubSubItem>* items) {
+ ParseItem(retract_elem, items);
+}
+
+void ParseEventItemsElem(const XmlElement* stanza,
+ std::vector<PubSubItem>* items) {
+ const XmlElement* event_elem = stanza->FirstNamed(QN_PUBSUB_EVENT);
+ if (event_elem != NULL) {
+ const XmlElement* items_elem =
+ event_elem->FirstNamed(QN_PUBSUB_EVENT_ITEMS);
+ if (items_elem != NULL) {
+ for (const XmlElement* item_elem =
+ items_elem->FirstNamed(QN_PUBSUB_EVENT_ITEM);
+ item_elem != NULL;
+ item_elem = item_elem->NextNamed(QN_PUBSUB_EVENT_ITEM)) {
+ ParseItem(item_elem, items);
+ }
+ for (const XmlElement* retract_elem =
+ items_elem->FirstNamed(QN_PUBSUB_EVENT_RETRACT);
+ retract_elem != NULL;
+ retract_elem = retract_elem->NextNamed(QN_PUBSUB_EVENT_RETRACT)) {
+ ParseRetract(retract_elem, items);
+ }
+ }
+ }
+}
+
+void ParsePubSubItemsElem(const XmlElement* stanza,
+ std::vector<PubSubItem>* items) {
+ const XmlElement* pubsub_elem = stanza->FirstNamed(QN_PUBSUB);
+ if (pubsub_elem != NULL) {
+ const XmlElement* items_elem = pubsub_elem->FirstNamed(QN_PUBSUB_ITEMS);
+ if (items_elem != NULL) {
+ for (const XmlElement* item_elem = items_elem->FirstNamed(QN_PUBSUB_ITEM);
+ item_elem != NULL;
+ item_elem = item_elem->NextNamed(QN_PUBSUB_ITEM)) {
+ ParseItem(item_elem, items);
+ }
+ }
+ }
+}
+
+} // namespace
+
+PubSubRequestTask::PubSubRequestTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : IqTask(parent, STR_GET, pubsubjid, CreatePubSubItemsElem(node)) {
+}
+
+void PubSubRequestTask::HandleResult(const XmlElement* stanza) {
+ std::vector<PubSubItem> items;
+ ParsePubSubItemsElem(stanza, &items);
+ SignalResult(this, items);
+}
+
+bool PubSubReceiveTask::WantsStanza(const XmlElement* stanza) {
+ return MatchStanzaFrom(stanza, pubsubjid_) &&
+ IsPubSubEventItemsElem(stanza, node_);
+}
+
+void PubSubReceiveTask::ReceiveStanza(const XmlElement* stanza) {
+ std::vector<PubSubItem> items;
+ ParseEventItemsElem(stanza, &items);
+ SignalUpdate(this, items);
+}
+
+PubSubPublishTask::PubSubPublishTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children)
+ : IqTask(parent, STR_SET, pubsubjid,
+ CreatePubSubPublishItemElem(node, itemid, children)),
+ itemid_(itemid) {
+}
+
+void PubSubPublishTask::HandleResult(const XmlElement* stanza) {
+ SignalResult(this);
+}
+
+PubSubRetractTask::PubSubRetractTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid)
+ : IqTask(parent, STR_SET, pubsubjid,
+ CreatePubSubRetractItemElem(node, itemid)),
+ itemid_(itemid) {
+}
+
+void PubSubRetractTask::HandleResult(const XmlElement* stanza) {
+ SignalResult(this);
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/pubsubtasks.h b/talk/xmpp/pubsubtasks.h
new file mode 100644
index 0000000..f0a1581
--- /dev/null
+++ b/talk/xmpp/pubsubtasks.h
@@ -0,0 +1,130 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_XMPP_PUBSUBTASKS_H_
+#define TALK_XMPP_PUBSUBTASKS_H_
+
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/iqtask.h"
+#include "talk/xmpp/receivetask.h"
+
+namespace buzz {
+
+// A PubSub itemid + payload. Useful for signaling items.
+struct PubSubItem {
+ std::string itemid;
+ // The entire <item>, owned by the stanza handler. To keep a
+ // reference after handling, make a copy.
+ const XmlElement* elem;
+};
+
+// An IqTask which gets a <pubsub><items> for a particular jid and
+// node, parses the items in the response and signals the items.
+class PubSubRequestTask : public IqTask {
+ public:
+ PubSubRequestTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node);
+
+ sigslot::signal2<PubSubRequestTask*,
+ const std::vector<PubSubItem>&> SignalResult;
+ // SignalError inherited by IqTask.
+ private:
+ virtual void HandleResult(const XmlElement* stanza);
+};
+
+// A ReceiveTask which listens for <event><items> of a particular
+// pubsub JID and node and then signals them items.
+class PubSubReceiveTask : public ReceiveTask {
+ public:
+ PubSubReceiveTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node)
+ : ReceiveTask(parent),
+ pubsubjid_(pubsubjid),
+ node_(node) {
+ }
+
+ sigslot::signal2<PubSubReceiveTask*,
+ const std::vector<PubSubItem>&> SignalUpdate;
+
+ protected:
+ virtual bool WantsStanza(const XmlElement* stanza);
+ virtual void ReceiveStanza(const XmlElement* stanza);
+
+ private:
+ Jid pubsubjid_;
+ std::string node_;
+};
+
+// An IqTask which publishes a <pubsub><publish><item> to a particular
+// pubsub jid and node.
+class PubSubPublishTask : public IqTask {
+ public:
+ // Takes ownership of children
+ PubSubPublishTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid,
+ const std::vector<XmlElement*>& children);
+
+ const std::string& itemid() const { return itemid_; }
+
+ sigslot::signal1<PubSubPublishTask*> SignalResult;
+
+ private:
+ // SignalError inherited by IqTask.
+ virtual void HandleResult(const XmlElement* stanza);
+
+ std::string itemid_;
+};
+
+// An IqTask which publishes a <pubsub><publish><retract> to a particular
+// pubsub jid and node.
+class PubSubRetractTask : public IqTask {
+ public:
+ PubSubRetractTask(XmppTaskParentInterface* parent,
+ const Jid& pubsubjid,
+ const std::string& node,
+ const std::string& itemid);
+
+ const std::string& itemid() const { return itemid_; }
+
+ sigslot::signal1<PubSubRetractTask*> SignalResult;
+
+ private:
+ // SignalError inherited by IqTask.
+ virtual void HandleResult(const XmlElement* stanza);
+
+ std::string itemid_;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_PUBSUBTASKS_H_
diff --git a/talk/xmpp/receivetask.cc b/talk/xmpp/receivetask.cc
new file mode 100644
index 0000000..53fac7e
--- /dev/null
+++ b/talk/xmpp/receivetask.cc
@@ -0,0 +1,51 @@
+/*
+ * libjingle
+ * Copyright 2011, 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 "talk/xmpp/receivetask.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+bool ReceiveTask::HandleStanza(const XmlElement* stanza) {
+ if (WantsStanza(stanza)) {
+ QueueStanza(stanza);
+ return true;
+ }
+
+ return false;
+}
+
+int ReceiveTask::ProcessStart() {
+ const XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ ReceiveStanza(stanza);
+ return STATE_START;
+}
+
+} // namespace buzz
diff --git a/talk/xmpp/receivetask.h b/talk/xmpp/receivetask.h
new file mode 100644
index 0000000..b18e0f0
--- /dev/null
+++ b/talk/xmpp/receivetask.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2011, 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.
+ */
+
+#ifndef TALK_XMPP_RECEIVETASK_H_
+#define TALK_XMPP_RECEIVETASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+// A base class for receiving stanzas. Override WantsStanza to
+// indicate that a stanza should be received and ReceiveStanza to
+// process it. Once started, ReceiveStanza will be called for all
+// stanzas that return true when passed to WantsStanza. This saves
+// you from having to remember how to setup the queueing and the task
+// states, etc.
+class ReceiveTask : public XmppTask {
+ public:
+ explicit ReceiveTask(XmppTaskParentInterface* parent) :
+ XmppTask(parent, XmppEngine::HL_TYPE) {}
+ virtual int ProcessStart();
+
+ protected:
+ virtual bool HandleStanza(const XmlElement* stanza);
+
+ // Return true if the stanza should be received.
+ virtual bool WantsStanza(const XmlElement* stanza) = 0;
+ // Process the received stanza.
+ virtual void ReceiveStanza(const XmlElement* stanza) = 0;
+};
+
+} // namespace buzz
+
+#endif // TALK_XMPP_RECEIVETASK_H_
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
index 578884a..09637c8 100644
--- a/talk/xmpp/xmppclient.cc
+++ b/talk/xmpp/xmppclient.cc
@@ -140,7 +140,7 @@
}
XmppEngine::State
-XmppClient::GetState() {
+XmppClient::GetState() const {
if (d_->engine_.get() == NULL)
return XmppEngine::STATE_NONE;
return d_->engine_->GetState();
@@ -266,6 +266,7 @@
XmppClient::Disconnect() {
if (d_->socket_.get() == NULL)
return XMPP_RETURN_BADSTATE;
+ Abort();
d_->engine_->Disconnect();
d_->socket_.reset(NULL);
return XMPP_RETURN_OK;
@@ -284,7 +285,7 @@
}
const Jid &
-XmppClient::jid() {
+XmppClient::jid() const {
return d_->engine_->FullJid();
}
diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h
index b8f854e..ad8b9a2 100644
--- a/talk/xmpp/xmppclient.h
+++ b/talk/xmpp/xmppclient.h
@@ -86,7 +86,6 @@
XmppReturnStatus Disconnect();
sigslot::signal1<XmppEngine::State> SignalStateChange;
- XmppEngine::State GetState();
XmppEngine::Error GetError(int *subcode);
// When there is a <stream:error> stanza, return the stanza
@@ -112,7 +111,8 @@
virtual XmppClientInterface* GetClient() { return this; }
// As XmppClientInterface
- virtual const Jid& jid();
+ virtual XmppEngine::State GetState() const;
+ virtual const Jid& jid() const;
virtual std::string NextId();
virtual XmppReturnStatus SendStanza(const XmlElement *stanza);
virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h
index fd82c79..6a88f98 100644
--- a/talk/xmpp/xmpptask.h
+++ b/talk/xmpp/xmpptask.h
@@ -73,7 +73,8 @@
XmppClientInterface();
virtual ~XmppClientInterface();
- virtual const Jid& jid() = 0;
+ virtual XmppEngine::State GetState() const = 0;
+ virtual const Jid& jid() const = 0;
virtual std::string NextId() = 0;
virtual XmppReturnStatus SendStanza(const XmlElement* stanza) = 0;
virtual XmppReturnStatus SendStanzaError(const XmlElement* original_stanza,