PacketSocketFactory and other updates for the chrome-sandbox branch.
git-svn-id: http://libjingle.googlecode.com/svn/branches/chrome-sandbox@53 dd674b97-3498-5ee5-1854-bdd07cd0ff33
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..e491a9e
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1 @@
+Google Inc.
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..e0c746e
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,53 @@
+Libjingle
+
+0.5.2 - Jan 11, 2010
+ - Fixed build on Windows 7 with VS 2010
+ - Fixed build on Windows x64
+ - Fixed build on Mac OSX
+ - Added option to examples/call to enable encryption
+ - Improved logging
+ - Bug fixes
+
+0.5.1 - Nov 2, 2010
+ - Added support for call encryption.
+ - Added addtional XEP-166 and XEP-167 features:
+ - Call redirection
+ - Candidates in session-accept or session-initiate
+ - Added support for bandwidth control.
+ - Added features in examples/call:
+ - bandwidth control on initiate or accept
+ - turn on/off SSL
+ - control signaling protocol
+ - send chat message
+
+0.5.0 - Sep 16, 2010
+ - Implemented Jingle protocols XEP-166 and XEP-167.
+ - Backward compatible with Google Talk Call Signaling protocol implemented
+ in previous versions.
+ - Builds on Windows, Linux, and Mac OS X with swtoolkit.
+ - Removed GipsLiteMediaEngine.
+ - Added video support.
+ - Added FileMediaEngine to support both voice and video via RTP dump.
+ - Many bug fixes.
+
+0.4.0 - Feb 01, 2007
+ - Updated protocol.
+ - Added relay server support.
+ - Added proxy detection support.
+ - Many other assorted changes.
+
+0.3.0 - Mar 16 2006
+ - New GipsLiteMediaEngine included to make calls using the GIPS
+ VoiceEngine Lite media componentry on Windows.
+
+0.2.0 - Jan 27 2006
+ - Windows build fixes with Visual Studio Express project files.
+ - Pseudo-TCP support provides TCP-like reliability over a P2PSocket
+ - TunnelSessionClient establishes sessions for reliably sending data
+ using Pseudo-TCP.
+ - A new pcp example application transfers files from one user to
+ another using TunnelSessionClient.
+ - TLS login support for both example applications.
+
+0.1.0 - Dec 15 2005
+ - Initial release.
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..d11f105
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,25 @@
+Copyright (c) 2004--2005, Google Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * 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.
+ * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT OWNER OR CONTRIBUTORS 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.
\ No newline at end of file
diff --git a/README b/README
new file mode 100644
index 0000000..f70b726
--- /dev/null
+++ b/README
@@ -0,0 +1,85 @@
+Libjingle
+
+1. Introduction
+
+Libjingle is a set of components provided by Google to implement Jingle
+protocols XEP-166 (http://xmpp.org/extensions/xep-0166.html) and XEP-167
+(http://xmpp.org/extensions/xep-0167.html). Libjingle is also backward
+compatible with Google Talk Call Signaling
+(http://code.google.com/apis/talk/call_signaling.html). This package will
+create several static libraries you may link to your projects as needed.
+
+-talk - No source files in talk/, just these subdirectories
+|-base - Contains basic low-level portable utility functions for
+| things like threads and sockets
+|-p2p - The P2P stack
+ |-base - Base p2p functionality
+ |-client - Hooks to tie it into XMPP
+|-session - Signaling
+ |-phone - Signaling code specific to making phone calls
+ |-testdata - Samples of RTP voice and video dump
+ |-tunnel - Tunnel session and channel
+|-xmllite - XML parser
+|-xmpp - XMPP engine
+
+In addition, this package contains two examples in talk/examples which
+illustrate the basic concepts of how the provided classes work.
+
+2. How to Build
+
+Libjingle is built with swtoolkit (http://code.google.com/p/swtoolkit/), which
+is a set of extensions to the open-source SCons build tool (www.scons.org).
+ * First, install Python 2.4 or later from http://www.python.org/.
+ Please note that since swtoolkit only works with Python 2.x, you will
+ not be able to use Python 3.x.
+
+ * Second, install the stand alone scons-local package 2.0.0 or later from
+ http://www.scons.org/download.php and set an environment variable,
+ SCONS_DIR, to point to the directory containing SCons, for example,
+ /src/libjingle/scons-local/scons-local-2.0.0.final.0/.
+
+ * Third, install swtoolkit from http://code.google.com/p/swtoolkit/.
+
+ * Finally, Libjingle depends on two open-source projects, expat and srtp.
+ Download expat from http://sourceforge.net/projects/expat/ to
+ talk/third_party/expat-2.0.1/. Follow the instructions at
+ http://sourceforge.net/projects/srtp/develop to download latest srtp to
+ talk/third_party/srtp. Note that srtp-1.4.4 does not work since it misses
+ the extensions used by Libjingle.
+ If you put expat or srtp in a different directory, you need to edit
+ talk/libjingle.scons correspondingly.
+
+2.1 Build Libjingle under Linux or OS X
+ * First, make sure the SCONS_DIR environment variable is set correctly.
+ * Second, run talk/third_party/expat-2.0.1/configure and
+ talk/third_party/srtp/configure.
+ * Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.sh. Run
+ $path_to_swtoolkit/hammer.sh --help for information on how to build for
+ different modes.
+
+2.2 Build Libjingle under Windows
+ * First, make sure the SCONS_DIR environment variable is set correctly and
+ Microsoft Visual Studio is installed.
+ * Second, copy talk/third_party/srtp/config.hw to
+ talk/third_party/srtp/crypto/include/config.h.
+ * Third, go to the talk/ directory and run $path_to_swtoolkit/hammer.bat. Run
+ $path_to_swtoolkit/hammer.sh --help for information on how to build for
+ different modes. You can run the last step under Visual Studio Command
+ Prompt if Visual Studio tools are not under the path environment variable.
+
+The built binaries are under talk/build/dbg/staging or talk/build/opt/staging,
+depending on the build mode. When the build is complete, you can run the
+examples, login or call. For the call sample, you can specify the input and
+output RTP dump for voice and video. This package provides two samples of input
+RTP dump: voice.rtpdump is a single channel, 16Khz voice encoded with G722, and
+video.rtpdump is 320x240 video encoded with H264 AVC at 30 frames per second.
+These provided samples will inter-operate with Google Talk Video. If you use
+other input RTP dump, you may need to change the codecs in call_main.cc, lines
+215 - 222.
+
+Libjingle also builds two server tools, a relay server and a STUN server. The
+relay server may be used to relay traffic when a direct peer-to-peer connection
+could not be established. The STUN Server implements the STUN protocol for
+Simple Traversal of UDP over NAT. See the Libjingle Developer Guide at
+http://code.google.com/apis/talk/index.html for information about configuring a
+client to use this relay server and this STUN server.
\ No newline at end of file
diff --git a/README.chrome-sandbox b/README.chrome-sandbox
new file mode 100644
index 0000000..5930d32
--- /dev/null
+++ b/README.chrome-sandbox
@@ -0,0 +1,2 @@
+The chrome-sandbox branch contains some experemental changes that are needed
+to make libjingle work in sandbox in Chrome.
diff --git a/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h b/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h
new file mode 100644
index 0000000..6ff97a6
--- /dev/null
+++ b/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h
@@ -0,0 +1,55 @@
+// This file is the Equifax Secure global eBusiness CA-1 certificate
+// in C form.
+
+// It was generated with the following command line:
+// > openssl x509 -in Equifax_Secure_Global_eBusiness_CA-1.cer -noout -C
+
+// The certificate was retrieved from:
+// http://www.geotrust.com/resources/root_certificates/certificates/Equifax_Secure_Global_eBusiness_CA-1.cer
+
+/* subject:/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+/* issuer :/C=US/O=Equifax Secure Inc./CN=Equifax Secure Global eBusiness CA-1 */
+unsigned char EquifaxSecureGlobalEBusinessCA1_certificate[660]={
+0x30,0x82,0x02,0x90,0x30,0x82,0x01,0xF9,0xA0,0x03,0x02,0x01,0x02,0x02,0x01,0x01,
+0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,0x30,
+0x5A,0x31,0x0B,0x30,0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,
+0x30,0x1A,0x06,0x03,0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,
+0x20,0x53,0x65,0x63,0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,
+0x06,0x03,0x55,0x04,0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,
+0x65,0x63,0x75,0x72,0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,
+0x73,0x69,0x6E,0x65,0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x1E,0x17,0x0D,0x39,
+0x39,0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x17,0x0D,0x32,0x30,
+0x30,0x36,0x32,0x31,0x30,0x34,0x30,0x30,0x30,0x30,0x5A,0x30,0x5A,0x31,0x0B,0x30,
+0x09,0x06,0x03,0x55,0x04,0x06,0x13,0x02,0x55,0x53,0x31,0x1C,0x30,0x1A,0x06,0x03,
+0x55,0x04,0x0A,0x13,0x13,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,
+0x75,0x72,0x65,0x20,0x49,0x6E,0x63,0x2E,0x31,0x2D,0x30,0x2B,0x06,0x03,0x55,0x04,
+0x03,0x13,0x24,0x45,0x71,0x75,0x69,0x66,0x61,0x78,0x20,0x53,0x65,0x63,0x75,0x72,
+0x65,0x20,0x47,0x6C,0x6F,0x62,0x61,0x6C,0x20,0x65,0x42,0x75,0x73,0x69,0x6E,0x65,
+0x73,0x73,0x20,0x43,0x41,0x2D,0x31,0x30,0x81,0x9F,0x30,0x0D,0x06,0x09,0x2A,0x86,
+0x48,0x86,0xF7,0x0D,0x01,0x01,0x01,0x05,0x00,0x03,0x81,0x8D,0x00,0x30,0x81,0x89,
+0x02,0x81,0x81,0x00,0xBA,0xE7,0x17,0x90,0x02,0x65,0xB1,0x34,0x55,0x3C,0x49,0xC2,
+0x51,0xD5,0xDF,0xA7,0xD1,0x37,0x8F,0xD1,0xE7,0x81,0x73,0x41,0x52,0x60,0x9B,0x9D,
+0xA1,0x17,0x26,0x78,0xAD,0xC7,0xB1,0xE8,0x26,0x94,0x32,0xB5,0xDE,0x33,0x8D,0x3A,
+0x2F,0xDB,0xF2,0x9A,0x7A,0x5A,0x73,0x98,0xA3,0x5C,0xE9,0xFB,0x8A,0x73,0x1B,0x5C,
+0xE7,0xC3,0xBF,0x80,0x6C,0xCD,0xA9,0xF4,0xD6,0x2B,0xC0,0xF7,0xF9,0x99,0xAA,0x63,
+0xA2,0xB1,0x47,0x02,0x0F,0xD4,0xE4,0x51,0x3A,0x12,0x3C,0x6C,0x8A,0x5A,0x54,0x84,
+0x70,0xDB,0xC1,0xC5,0x90,0xCF,0x72,0x45,0xCB,0xA8,0x59,0xC0,0xCD,0x33,0x9D,0x3F,
+0xA3,0x96,0xEB,0x85,0x33,0x21,0x1C,0x3E,0x1E,0x3E,0x60,0x6E,0x76,0x9C,0x67,0x85,
+0xC5,0xC8,0xC3,0x61,0x02,0x03,0x01,0x00,0x01,0xA3,0x66,0x30,0x64,0x30,0x11,0x06,
+0x09,0x60,0x86,0x48,0x01,0x86,0xF8,0x42,0x01,0x01,0x04,0x04,0x03,0x02,0x00,0x07,
+0x30,0x0F,0x06,0x03,0x55,0x1D,0x13,0x01,0x01,0xFF,0x04,0x05,0x30,0x03,0x01,0x01,
+0xFF,0x30,0x1F,0x06,0x03,0x55,0x1D,0x23,0x04,0x18,0x30,0x16,0x80,0x14,0xBE,0xA8,
+0xA0,0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,
+0x68,0x6C,0x30,0x1D,0x06,0x03,0x55,0x1D,0x0E,0x04,0x16,0x04,0x14,0xBE,0xA8,0xA0,
+0x74,0x72,0x50,0x6B,0x44,0xB7,0xC9,0x23,0xD8,0xFB,0xA8,0xFF,0xB3,0x57,0x6B,0x68,
+0x6C,0x30,0x0D,0x06,0x09,0x2A,0x86,0x48,0x86,0xF7,0x0D,0x01,0x01,0x04,0x05,0x00,
+0x03,0x81,0x81,0x00,0x30,0xE2,0x01,0x51,0xAA,0xC7,0xEA,0x5F,0xDA,0xB9,0xD0,0x65,
+0x0F,0x30,0xD6,0x3E,0xDA,0x0D,0x14,0x49,0x6E,0x91,0x93,0x27,0x14,0x31,0xEF,0xC4,
+0xF7,0x2D,0x45,0xF8,0xEC,0xC7,0xBF,0xA2,0x41,0x0D,0x23,0xB4,0x92,0xF9,0x19,0x00,
+0x67,0xBD,0x01,0xAF,0xCD,0xE0,0x71,0xFC,0x5A,0xCF,0x64,0xC4,0xE0,0x96,0x98,0xD0,
+0xA3,0x40,0xE2,0x01,0x8A,0xEF,0x27,0x07,0xF1,0x65,0x01,0x8A,0x44,0x2D,0x06,0x65,
+0x75,0x52,0xC0,0x86,0x10,0x20,0x21,0x5F,0x6C,0x6B,0x0F,0x6C,0xAE,0x09,0x1C,0xAF,
+0xF2,0xA2,0x18,0x34,0xC4,0x75,0xA4,0x73,0x1C,0xF1,0x8D,0xDC,0xEF,0xAD,0xF9,0xB3,
+0x76,0xB4,0x92,0xBF,0xDC,0x95,0x10,0x1E,0xBE,0xCB,0xC8,0x3B,0x5A,0x84,0x60,0x19,
+0x56,0x94,0xA9,0x55,
+};
diff --git a/talk/base/asyncfile.cc b/talk/base/asyncfile.cc
new file mode 100644
index 0000000..5c6e11d
--- /dev/null
+++ b/talk/base/asyncfile.cc
@@ -0,0 +1,38 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/asyncfile.h"
+
+namespace talk_base {
+
+AsyncFile::AsyncFile() {
+}
+
+AsyncFile::~AsyncFile() {
+}
+
+} // namespace talk_base
diff --git a/talk/base/asyncfile.h b/talk/base/asyncfile.h
new file mode 100644
index 0000000..8af52be
--- /dev/null
+++ b/talk/base/asyncfile.h
@@ -0,0 +1,57 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_ASYNCFILE_H__
+#define TALK_BASE_ASYNCFILE_H__
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+// Provides the ability to perform file I/O asynchronously.
+// TODO: Create a common base class with AsyncSocket.
+class AsyncFile {
+ public:
+ AsyncFile();
+ virtual ~AsyncFile();
+
+ // Determines whether the file will receive read events.
+ virtual bool readable() = 0;
+ virtual void set_readable(bool value) = 0;
+
+ // Determines whether the file will receive write events.
+ virtual bool writable() = 0;
+ virtual void set_writable(bool value) = 0;
+
+ sigslot::signal1<AsyncFile*> SignalReadEvent;
+ sigslot::signal1<AsyncFile*> SignalWriteEvent;
+ sigslot::signal2<AsyncFile*, int> SignalCloseEvent;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCFILE_H__
diff --git a/talk/base/asynchttprequest.cc b/talk/base/asynchttprequest.cc
new file mode 100644
index 0000000..1e05d82
--- /dev/null
+++ b/talk/base/asynchttprequest.cc
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/asynchttprequest.h"
+
+namespace talk_base {
+
+enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE };
+static const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent)
+ : firewall_(NULL), port_(80), secure_(false),
+ timeout_(kDefaultHTTPTimeout), fail_redirect_(false),
+ factory_(Thread::Current()->socketserver(), user_agent),
+ pool_(&factory_), client_(user_agent.c_str(), &pool_), error_(HE_NONE) {
+ client_.SignalHttpClientComplete.connect(this,
+ &AsyncHttpRequest::OnComplete);
+}
+
+AsyncHttpRequest::~AsyncHttpRequest() {
+}
+
+void AsyncHttpRequest::OnWorkStart() {
+ factory_.SetProxy(proxy_);
+ if (secure_)
+ factory_.UseSSL(host_.c_str());
+
+ bool transparent_proxy = (port_ == 80) &&
+ ((proxy_.type == PROXY_HTTPS) || (proxy_.type == PROXY_UNKNOWN));
+ if (transparent_proxy) {
+ client_.set_proxy(proxy_);
+ }
+ client_.set_fail_redirect(fail_redirect_);
+ client_.set_server(SocketAddress(host_, port_));
+
+ LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+ Thread::Current()->PostDelayed(timeout_, this, MSG_TIMEOUT);
+ client_.start();
+}
+
+void AsyncHttpRequest::OnWorkStop() {
+ // worker is already quitting, no need to explicitly quit
+ LOG(LS_INFO) << "HttpRequest cancelled";
+}
+
+void AsyncHttpRequest::OnComplete(HttpClient* client, HttpErrorType error) {
+ Thread::Current()->Clear(this, MSG_TIMEOUT);
+
+ set_error(error);
+ if (!error) {
+ LOG(LS_INFO) << "HttpRequest completed successfully";
+
+ std::string value;
+ if (client_.response().hasHeader(HH_LOCATION, &value)) {
+ response_redirect_ = value.c_str();
+ }
+ } else {
+ LOG(LS_INFO) << "HttpRequest completed with error: " << error;
+ }
+
+ worker()->Quit();
+}
+
+void AsyncHttpRequest::OnMessage(Message* message) {
+ if (message->message_id != MSG_TIMEOUT) {
+ SignalThread::OnMessage(message);
+ return;
+ }
+
+ LOG(LS_INFO) << "HttpRequest timed out";
+ client_.reset();
+ worker()->Quit();
+}
+
+void AsyncHttpRequest::DoWork() {
+ // Do nothing while we wait for the request to finish. We only do this so
+ // that we can be a SignalThread; in the future this class should not be
+ // a SignalThread, since it does not need to spawn a new thread.
+ Thread::Current()->ProcessMessages(kForever);
+}
+
+} // namespace talk_base
diff --git a/talk/base/asynchttprequest.h b/talk/base/asynchttprequest.h
new file mode 100644
index 0000000..f5d7f46
--- /dev/null
+++ b/talk/base/asynchttprequest.h
@@ -0,0 +1,112 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_ASYNCHTTPREQUEST_H_
+#define TALK_BASE_ASYNCHTTPREQUEST_H_
+
+#include <string>
+#include "talk/base/event.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/signalthread.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+///////////////////////////////////////////////////////////////////////////////
+// AsyncHttpRequest
+// Performs an HTTP request on a background thread. Notifies on the foreground
+// thread once the request is done (successfully or unsuccessfully).
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncHttpRequest : public SignalThread {
+ public:
+ explicit AsyncHttpRequest(const std::string &user_agent);
+ ~AsyncHttpRequest();
+
+ void set_proxy(const ProxyInfo& proxy) {
+ proxy_ = proxy;
+ }
+ void set_firewall(FirewallManager * firewall) {
+ firewall_ = firewall;
+ }
+
+ // The DNS name of the host to connect to.
+ const std::string& host() { return host_; }
+ void set_host(const std::string& host) { host_ = host; }
+
+ // The port to connect to on the target host.
+ int port() { return port_; }
+ void set_port(int port) { port_ = port; }
+
+ // Whether the request should use SSL.
+ bool secure() { return secure_; }
+ void set_secure(bool secure) { secure_ = secure; }
+
+ // Time to wait on the download, in ms.
+ int timeout() { return timeout_; }
+ void set_timeout(int timeout) { timeout_ = timeout; }
+
+ // Fail redirects to allow analysis of redirect urls, etc.
+ bool fail_redirect() const { return fail_redirect_; }
+ void set_fail_redirect(bool redirect) { fail_redirect_ = redirect; }
+
+ // Returns the redirect when redirection occurs
+ const std::string& response_redirect() { return response_redirect_; }
+
+ HttpRequestData& request() { return client_.request(); }
+ HttpResponseData& response() { return client_.response(); }
+ HttpErrorType error() { return error_; }
+
+ protected:
+ void set_error(HttpErrorType error) { error_ = error; }
+ virtual void OnWorkStart();
+ virtual void OnWorkStop();
+ void OnComplete(HttpClient* client, HttpErrorType error);
+ virtual void OnMessage(Message* message);
+ virtual void DoWork();
+
+ private:
+ ProxyInfo proxy_;
+ FirewallManager* firewall_;
+ std::string host_;
+ int port_;
+ bool secure_;
+ int timeout_;
+ bool fail_redirect_;
+ SslSocketFactory factory_;
+ ReuseSocketPool pool_;
+ HttpClient client_;
+ HttpErrorType error_;
+ std::string response_redirect_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCHTTPREQUEST_H_
diff --git a/talk/base/asyncpacketsocket.h b/talk/base/asyncpacketsocket.h
new file mode 100644
index 0000000..47a1bf3
--- /dev/null
+++ b/talk/base/asyncpacketsocket.h
@@ -0,0 +1,91 @@
+/*
+ * 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_ASYNCPACKETSOCKET_H_
+#define TALK_BASE_ASYNCPACKETSOCKET_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously. Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncPacketSocket : public sigslot::has_slots<> {
+ public:
+ AsyncPacketSocket() { }
+ virtual ~AsyncPacketSocket() { }
+
+ // Returns current local address. If port or IP address is not
+ // assigned yet, then they set to 0 in the result and |allocated| is
+ // set to false. Otherwise |allocated| is set to true.
+ virtual SocketAddress GetLocalAddress(bool* allocated) const = 0;
+
+ // Returns remote address. Returns zeroes if this is not a client TCP socket.
+ virtual SocketAddress GetRemoteAddress() const = 0;
+
+ // Send a packet.
+ virtual int Send(const void *pv, size_t cb) = 0;
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+
+ // Close the socket.
+ virtual int Close() = 0;
+
+ // Returns current state of the socket.
+ virtual Socket::ConnState GetState() const = 0;
+
+ // Get/set options.
+ virtual int GetOption(Socket::Option opt, int* value) = 0;
+ virtual int SetOption(Socket::Option opt, int value) = 0;
+
+ // Get/Set current error.
+ // TODO: Do we really need SetError() here?
+ virtual int GetError() const = 0;
+ virtual void SetError(int error) = 0;
+
+ // Emitted after address for the socket is allocated.
+ sigslot::signal2<AsyncPacketSocket*, const SocketAddress&> SignalAddressReady;
+
+ // Emitted each time a packet is read. Used only for UDP and
+ // connected TCP sockets.
+ sigslot::signal4<AsyncPacketSocket*, const char*, size_t,
+ const SocketAddress&> SignalReadPacket;
+
+ // Used only for connected TCP sockets.
+ sigslot::signal1<AsyncPacketSocket*> SignalConnect;
+ sigslot::signal2<AsyncPacketSocket*, int> SignalClose;
+
+ // Used only for listening TCP sockets.
+ sigslot::signal2<AsyncPacketSocket*, AsyncPacketSocket*> SignalNewConnection;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncPacketSocket);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCPACKETSOCKET_H_
diff --git a/talk/base/asyncsocket.cc b/talk/base/asyncsocket.cc
new file mode 100644
index 0000000..d9ed94c
--- /dev/null
+++ b/talk/base/asyncsocket.cc
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/asyncsocket.h"
+
+namespace talk_base {
+
+AsyncSocket::AsyncSocket() {
+}
+
+AsyncSocket::~AsyncSocket() {
+}
+
+AsyncSocketAdapter::AsyncSocketAdapter(AsyncSocket* socket) : socket_(NULL) {
+ Attach(socket);
+}
+
+AsyncSocketAdapter::~AsyncSocketAdapter() {
+ delete socket_;
+}
+
+void AsyncSocketAdapter::Attach(AsyncSocket* socket) {
+ ASSERT(!socket_);
+ socket_ = socket;
+ if (socket_) {
+ socket_->SignalConnectEvent.connect(this,
+ &AsyncSocketAdapter::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this,
+ &AsyncSocketAdapter::OnReadEvent);
+ socket_->SignalWriteEvent.connect(this,
+ &AsyncSocketAdapter::OnWriteEvent);
+ socket_->SignalCloseEvent.connect(this,
+ &AsyncSocketAdapter::OnCloseEvent);
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/asyncsocket.h b/talk/base/asyncsocket.h
new file mode 100644
index 0000000..3d12984
--- /dev/null
+++ b/talk/base/asyncsocket.h
@@ -0,0 +1,133 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_ASYNCSOCKET_H_
+#define TALK_BASE_ASYNCSOCKET_H_
+
+#include "talk/base/common.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace talk_base {
+
+// TODO: Remove Socket and rename AsyncSocket to Socket.
+
+// Provides the ability to perform socket I/O asynchronously.
+class AsyncSocket : public Socket {
+ public:
+ AsyncSocket();
+ virtual ~AsyncSocket();
+
+ virtual AsyncSocket* Accept(SocketAddress* paddr) = 0;
+
+ sigslot::signal1<AsyncSocket*> SignalReadEvent; // ready to read
+ sigslot::signal1<AsyncSocket*> SignalWriteEvent; // ready to write
+ sigslot::signal1<AsyncSocket*> SignalConnectEvent; // connected
+ sigslot::signal2<AsyncSocket*, int> SignalCloseEvent; // closed
+};
+
+class AsyncSocketAdapter : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+ // The adapted socket may explicitly be NULL, and later assigned using Attach.
+ // However, subclasses which support detached mode must override any methods
+ // that will be called during the detached period (usually GetState()), to
+ // avoid dereferencing a null pointer.
+ explicit AsyncSocketAdapter(AsyncSocket* socket);
+ virtual ~AsyncSocketAdapter();
+ void Attach(AsyncSocket* socket);
+ virtual SocketAddress GetLocalAddress() const {
+ return socket_->GetLocalAddress();
+ }
+ virtual SocketAddress GetRemoteAddress() const {
+ return socket_->GetRemoteAddress();
+ }
+ virtual int Bind(const SocketAddress& addr) {
+ return socket_->Bind(addr);
+ }
+ virtual int Connect(const SocketAddress& addr) {
+ return socket_->Connect(addr);
+ }
+ virtual int Send(const void* pv, size_t cb) {
+ return socket_->Send(pv, cb);
+ }
+ virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+ return socket_->SendTo(pv, cb, addr);
+ }
+ virtual int Recv(void* pv, size_t cb) {
+ return socket_->Recv(pv, cb);
+ }
+ virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+ return socket_->RecvFrom(pv, cb, paddr);
+ }
+ virtual int Listen(int backlog) {
+ return socket_->Listen(backlog);
+ }
+ virtual AsyncSocket* Accept(SocketAddress* paddr) {
+ return socket_->Accept(paddr);
+ }
+ virtual int Close() {
+ return socket_->Close();
+ }
+ virtual int GetError() const {
+ return socket_->GetError();
+ }
+ virtual void SetError(int error) {
+ return socket_->SetError(error);
+ }
+ virtual ConnState GetState() const {
+ return socket_->GetState();
+ }
+ virtual int EstimateMTU(uint16* mtu) {
+ return socket_->EstimateMTU(mtu);
+ }
+ virtual int GetOption(Option opt, int* value) {
+ return socket_->GetOption(opt, value);
+ }
+ virtual int SetOption(Option opt, int value) {
+ return socket_->SetOption(opt, value);
+ }
+
+ protected:
+ virtual void OnConnectEvent(AsyncSocket* socket) {
+ SignalConnectEvent(this);
+ }
+ virtual void OnReadEvent(AsyncSocket* socket) {
+ SignalReadEvent(this);
+ }
+ virtual void OnWriteEvent(AsyncSocket* socket) {
+ SignalWriteEvent(this);
+ }
+ virtual void OnCloseEvent(AsyncSocket* socket, int err) {
+ SignalCloseEvent(this, err);
+ }
+
+ AsyncSocket* socket_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCSOCKET_H_
diff --git a/talk/base/asynctcpsocket.cc b/talk/base/asynctcpsocket.cc
new file mode 100644
index 0000000..03fc01a
--- /dev/null
+++ b/talk/base/asynctcpsocket.cc
@@ -0,0 +1,268 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/asynctcpsocket.h"
+
+#include <cstring>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif // POSIX
+
+namespace talk_base {
+
+static const size_t MAX_PACKET_SIZE = 64 * 1024;
+
+typedef uint16 PacketLength;
+static const size_t PKT_LEN_SIZE = sizeof(PacketLength);
+
+static const size_t BUF_SIZE = MAX_PACKET_SIZE + PKT_LEN_SIZE;
+
+static const int LISTEN_BACKLOG = 5;
+
+AsyncTCPSocket* AsyncTCPSocket::Create(SocketFactory* factory, bool listen) {
+ AsyncSocket* sock = factory->CreateAsyncSocket(SOCK_STREAM);
+ // This will still return a socket even if we failed to listen on
+ // it. It is neccessary because even if we can't accept new
+ // connections on this socket, the corresponding port is still
+ // useful for outgoing connections.
+ //
+ // TODO: It might be better to pass listen() error to the
+ // upper layer and let it handle the problem.
+ return (sock) ? new AsyncTCPSocket(sock, listen) : NULL;
+}
+
+AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket, bool listen)
+ : socket_(socket),
+ listen_(listen),
+ insize_(BUF_SIZE),
+ inpos_(0),
+ outsize_(BUF_SIZE),
+ outpos_(0) {
+ inbuf_ = new char[insize_];
+ outbuf_ = new char[outsize_];
+
+ ASSERT(socket_.get() != NULL);
+ socket_->SignalConnectEvent.connect(this, &AsyncTCPSocket::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this, &AsyncTCPSocket::OnReadEvent);
+ socket_->SignalWriteEvent.connect(this, &AsyncTCPSocket::OnWriteEvent);
+ socket_->SignalCloseEvent.connect(this, &AsyncTCPSocket::OnCloseEvent);
+
+ if (listen_) {
+ if (socket_->Listen(LISTEN_BACKLOG) < 0) {
+ LOG(LS_ERROR) << "Listen() failed with error " << socket_->GetError();
+ }
+ }
+}
+
+AsyncTCPSocket::~AsyncTCPSocket() {
+ delete [] inbuf_;
+ delete [] outbuf_;
+}
+
+SocketAddress AsyncTCPSocket::GetLocalAddress(bool* allocated) const {
+ if (allocated)
+ *allocated = true;
+ return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncTCPSocket::GetRemoteAddress() const {
+ return socket_->GetRemoteAddress();
+}
+
+int AsyncTCPSocket::Send(const void *pv, size_t cb) {
+ if (cb > MAX_PACKET_SIZE) {
+ socket_->SetError(EMSGSIZE);
+ return -1;
+ }
+
+ // If we are blocking on send, then silently drop this packet
+ if (outpos_)
+ return static_cast<int>(cb);
+
+ PacketLength pkt_len = HostToNetwork16(static_cast<PacketLength>(cb));
+ memcpy(outbuf_, &pkt_len, PKT_LEN_SIZE);
+ memcpy(outbuf_ + PKT_LEN_SIZE, pv, cb);
+ outpos_ = PKT_LEN_SIZE + cb;
+
+ int res = Flush();
+ if (res <= 0) {
+ // drop packet if we made no progress
+ outpos_ = 0;
+ return res;
+ }
+
+ // We claim to have sent the whole thing, even if we only sent partial
+ return static_cast<int>(cb);
+}
+
+int AsyncTCPSocket::SendTo(const void *pv, size_t cb,
+ const SocketAddress& addr) {
+ if (addr == GetRemoteAddress())
+ return Send(pv, cb);
+
+ ASSERT(false);
+ socket_->SetError(ENOTCONN);
+ return -1;
+}
+
+int AsyncTCPSocket::Close() {
+ return socket_->Close();
+}
+
+Socket::ConnState AsyncTCPSocket::GetState() const {
+ return socket_->GetState();
+}
+
+int AsyncTCPSocket::GetOption(Socket::Option opt, int* value) {
+ return socket_->GetOption(opt, value);
+}
+
+int AsyncTCPSocket::SetOption(Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int AsyncTCPSocket::GetError() const {
+ return socket_->GetError();
+}
+
+void AsyncTCPSocket::SetError(int error) {
+ return socket_->SetError(error);
+}
+
+int AsyncTCPSocket::SendRaw(const void * pv, size_t cb) {
+ if (outpos_ + cb > outsize_) {
+ socket_->SetError(EMSGSIZE);
+ return -1;
+ }
+
+ memcpy(outbuf_ + outpos_, pv, cb);
+ outpos_ += cb;
+
+ return Flush();
+}
+
+void AsyncTCPSocket::ProcessInput(char * data, size_t& len) {
+ SocketAddress remote_addr(GetRemoteAddress());
+
+ while (true) {
+ if (len < PKT_LEN_SIZE)
+ return;
+
+ PacketLength pkt_len;
+ memcpy(&pkt_len, data, PKT_LEN_SIZE);
+ pkt_len = NetworkToHost16(pkt_len);
+
+ if (len < PKT_LEN_SIZE + pkt_len)
+ return;
+
+ SignalReadPacket(this, data + PKT_LEN_SIZE, pkt_len, remote_addr);
+
+ len -= PKT_LEN_SIZE + pkt_len;
+ if (len > 0) {
+ memmove(data, data + PKT_LEN_SIZE + pkt_len, len);
+ }
+ }
+}
+
+int AsyncTCPSocket::Flush() {
+ int res = socket_->Send(outbuf_, outpos_);
+ if (res <= 0) {
+ return res;
+ }
+ if (static_cast<size_t>(res) <= outpos_) {
+ outpos_ -= res;
+ } else {
+ ASSERT(false);
+ return -1;
+ }
+ if (outpos_ > 0) {
+ memmove(outbuf_, outbuf_ + res, outpos_);
+ }
+ return res;
+}
+
+void AsyncTCPSocket::OnConnectEvent(AsyncSocket* socket) {
+ SignalConnect(this);
+}
+
+void AsyncTCPSocket::OnReadEvent(AsyncSocket* socket) {
+ ASSERT(socket_.get() == socket);
+
+ if (listen_) {
+ talk_base::SocketAddress address;
+ talk_base::AsyncSocket* new_socket = socket->Accept(&address);
+ if (!new_socket) {
+ // TODO: Do something better like forwarding the error
+ // to the user.
+ LOG(LS_ERROR) << "TCP accept failed with error " << socket_->GetError();
+ return;
+ }
+
+ SignalNewConnection(this, new AsyncTCPSocket(new_socket, false));
+
+ // Prime a read event in case data is waiting.
+ new_socket->SignalReadEvent(new_socket);
+ } else {
+ int len = socket_->Recv(inbuf_ + inpos_, insize_ - inpos_);
+ if (len < 0) {
+ // TODO: Do something better like forwarding the error to the user.
+ if (!socket_->IsBlocking()) {
+ LOG(LS_ERROR) << "Recv() returned error: " << socket_->GetError();
+ }
+ return;
+ }
+
+ inpos_ += len;
+
+ ProcessInput(inbuf_, inpos_);
+
+ if (inpos_ >= insize_) {
+ LOG(LS_ERROR) << "input buffer overflow";
+ ASSERT(false);
+ inpos_ = 0;
+ }
+ }
+}
+
+void AsyncTCPSocket::OnWriteEvent(AsyncSocket* socket) {
+ ASSERT(socket_.get() == socket);
+
+ if (outpos_ > 0) {
+ Flush();
+ }
+}
+
+void AsyncTCPSocket::OnCloseEvent(AsyncSocket* socket, int error) {
+ SignalClose(this, error);
+}
+
+} // namespace talk_base
diff --git a/talk/base/asynctcpsocket.h b/talk/base/asynctcpsocket.h
new file mode 100644
index 0000000..06bbbf4
--- /dev/null
+++ b/talk/base/asynctcpsocket.h
@@ -0,0 +1,81 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_ASYNCTCPSOCKET_H_
+#define TALK_BASE_ASYNCTCPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Simulates UDP semantics over TCP. Send and Recv packet sizes
+// are preserved, and drops packets silently on Send, rather than
+// buffer them in user space.
+class AsyncTCPSocket : public AsyncPacketSocket {
+ public:
+ static AsyncTCPSocket* Create(SocketFactory* factory, bool listen);
+ explicit AsyncTCPSocket(AsyncSocket* socket, bool listen);
+ virtual ~AsyncTCPSocket();
+
+ virtual SocketAddress GetLocalAddress(bool* allocated) const;
+ virtual SocketAddress GetRemoteAddress() const;
+ virtual int Send(const void *pv, size_t cb);
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+ virtual int Close();
+
+ virtual Socket::ConnState GetState() const;
+ virtual int GetOption(Socket::Option opt, int* value);
+ virtual int SetOption(Socket::Option opt, int value);
+ virtual int GetError() const;
+ virtual void SetError(int error);
+
+ protected:
+ int SendRaw(const void* pv, size_t cb);
+ virtual void ProcessInput(char* data, size_t& len);
+
+ private:
+ int Flush();
+
+ // Called by the underlying socket
+ void OnConnectEvent(AsyncSocket* socket);
+ void OnReadEvent(AsyncSocket* socket);
+ void OnWriteEvent(AsyncSocket* socket);
+ void OnCloseEvent(AsyncSocket* socket, int error);
+
+ scoped_ptr<AsyncSocket> socket_;
+ bool listen_;
+ char* inbuf_, * outbuf_;
+ size_t insize_, inpos_, outsize_, outpos_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncTCPSocket);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCTCPSOCKET_H_
diff --git a/talk/base/asyncudpsocket.cc b/talk/base/asyncudpsocket.cc
new file mode 100644
index 0000000..dd2dc64
--- /dev/null
+++ b/talk/base/asyncudpsocket.cc
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+static const int BUF_SIZE = 64 * 1024;
+
+AsyncUDPSocket* AsyncUDPSocket::Create(SocketFactory* factory,
+ const SocketAddress& address) {
+ scoped_ptr<AsyncSocket> socket(factory->CreateAsyncSocket(SOCK_DGRAM));
+ if (!socket.get())
+ return NULL;
+ if (socket->Bind(address)) {
+ LOG(LS_INFO) << "Failed to bind UDP socket " << socket->GetError();
+ return NULL;
+ }
+ return new AsyncUDPSocket(socket.release());
+}
+
+AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket)
+ : socket_(socket) {
+ ASSERT(socket_.get() != NULL);
+ size_ = BUF_SIZE;
+ buf_ = new char[size_];
+
+ // The socket should start out readable but not writable.
+ socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent);
+}
+
+AsyncUDPSocket::~AsyncUDPSocket() {
+ delete [] buf_;
+}
+
+SocketAddress AsyncUDPSocket::GetLocalAddress(bool* allocated) const {
+ if (allocated)
+ *allocated = true;
+ return socket_->GetLocalAddress();
+}
+
+SocketAddress AsyncUDPSocket::GetRemoteAddress() const {
+ return socket_->GetRemoteAddress();
+}
+
+int AsyncUDPSocket::Send(const void *pv, size_t cb) {
+ return socket_->Send(pv, cb);
+}
+
+int AsyncUDPSocket::SendTo(
+ const void *pv, size_t cb, const SocketAddress& addr) {
+ return socket_->SendTo(pv, cb, addr);
+}
+
+int AsyncUDPSocket::Close() {
+ return socket_->Close();
+}
+
+Socket::ConnState AsyncUDPSocket::GetState() const {
+ return socket_->GetState();
+}
+
+int AsyncUDPSocket::GetOption(Socket::Option opt, int* value) {
+ return socket_->GetOption(opt, value);
+}
+
+int AsyncUDPSocket::SetOption(Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int AsyncUDPSocket::GetError() const {
+ return socket_->GetError();
+}
+
+void AsyncUDPSocket::SetError(int error) {
+ return socket_->SetError(error);
+}
+
+void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) {
+ ASSERT(socket_.get() == socket);
+
+ SocketAddress remote_addr;
+ int len = socket_->RecvFrom(buf_, size_, &remote_addr);
+ if (len < 0) {
+ // An error here typically means we got an ICMP error in response to our
+ // send datagram, indicating the remote address was unreachable.
+ // When doing ICE, this kind of thing will often happen.
+ // TODO: Do something better like forwarding the error to the user.
+ SocketAddress local_addr = socket_->GetLocalAddress();
+ LOG(LS_INFO) << "AsyncUDPSocket[" << local_addr.ToString() << "] "
+ << "receive failed with error " << socket_->GetError();
+ return;
+ }
+
+ // TODO: Make sure that we got all of the packet.
+ // If we did not, then we should resize our buffer to be large enough.
+ SignalReadPacket(this, buf_, (size_t)len, remote_addr);
+}
+
+} // namespace talk_base
diff --git a/talk/base/asyncudpsocket.h b/talk/base/asyncudpsocket.h
new file mode 100644
index 0000000..e3012a5
--- /dev/null
+++ b/talk/base/asyncudpsocket.h
@@ -0,0 +1,71 @@
+/*
+ * 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_ASYNCUDPSOCKET_H_
+#define TALK_BASE_ASYNCUDPSOCKET_H_
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+// Provides the ability to receive packets asynchronously. Sends are not
+// buffered since it is acceptable to drop packets under high load.
+class AsyncUDPSocket : public AsyncPacketSocket {
+ public:
+ // Creates a new socket for sending asynchronous UDP packets using an
+ // asynchronous socket from the given factory.
+ static AsyncUDPSocket* Create(SocketFactory* factory,
+ const SocketAddress& address);
+ explicit AsyncUDPSocket(AsyncSocket* socket);
+ virtual ~AsyncUDPSocket();
+
+ virtual SocketAddress GetLocalAddress(bool* allocated) const;
+ virtual SocketAddress GetRemoteAddress() const;
+ virtual int Send(const void *pv, size_t cb);
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+ virtual int Close();
+
+ virtual Socket::ConnState GetState() const;
+ virtual int GetOption(Socket::Option opt, int* value);
+ virtual int SetOption(Socket::Option opt, int value);
+ virtual int GetError() const;
+ virtual void SetError(int error);
+
+ private:
+ // Called when the underlying socket is ready to be read from.
+ void OnReadEvent(AsyncSocket* socket);
+
+ scoped_ptr<AsyncSocket> socket_;
+ char* buf_;
+ size_t size_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_ASYNCUDPSOCKET_H_
diff --git a/talk/base/autodetectproxy.cc b/talk/base/autodetectproxy.cc
new file mode 100644
index 0000000..ffcac26
--- /dev/null
+++ b/talk/base/autodetectproxy.cc
@@ -0,0 +1,187 @@
+/*
+ * 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 "talk/base/autodetectproxy.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/proxydetect.h"
+
+namespace talk_base {
+
+enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE };
+
+static const ProxyType TEST_ORDER[] = {
+ PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+};
+
+AutoDetectProxy::AutoDetectProxy(const std::string& user_agent)
+ : agent_(user_agent), socket_(NULL), next_(0) {
+}
+
+AutoDetectProxy::~AutoDetectProxy() {
+}
+
+void AutoDetectProxy::DoWork() {
+ // TODO: Try connecting to server_url without proxy first here?
+ if (!server_url_.empty()) {
+ LOG(LS_INFO) << "GetProxySettingsForUrl(" << server_url_ << ") - start";
+ GetProxySettingsForUrl(agent_.c_str(), server_url_.c_str(), proxy_, true);
+ LOG(LS_INFO) << "GetProxySettingsForUrl - stop";
+ }
+ Url<char> url(proxy_.address.IPAsString());
+ if (url.valid()) {
+ LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host";
+ proxy_.address.SetIP(url.host());
+ }
+ LOG(LS_INFO) << "AutoDetectProxy found proxy at " << proxy_.address;
+ if (proxy_.type == PROXY_UNKNOWN) {
+ LOG(LS_INFO) << "AutoDetectProxy initiating proxy classification";
+ Next();
+ // Process I/O until Stop()
+ Thread::Current()->ProcessMessages(kForever);
+ // Clean up the autodetect socket, from the thread that created it
+ delete socket_;
+ }
+ // TODO: If we found a proxy, try to use it to verify that it
+ // works by sending a request to server_url. This could either be
+ // done here or by the HttpPortAllocator.
+}
+
+void AutoDetectProxy::OnMessage(Message *msg) {
+ if (MSG_TIMEOUT == msg->message_id) {
+ OnCloseEvent(socket_, ETIMEDOUT);
+ } else {
+ SignalThread::OnMessage(msg);
+ }
+}
+
+void AutoDetectProxy::Next() {
+ if (TEST_ORDER[next_] >= PROXY_UNKNOWN) {
+ Complete(PROXY_UNKNOWN);
+ return;
+ }
+
+ LOG(LS_VERBOSE) << "AutoDetectProxy connecting to "
+ << proxy_.address.ToString();
+
+ if (socket_) {
+ Thread::Current()->Clear(this, MSG_TIMEOUT);
+ socket_->Close();
+ Thread::Current()->Dispose(socket_);
+ socket_ = NULL;
+ }
+
+ socket_ = Thread::Current()->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+ socket_->SignalConnectEvent.connect(this, &AutoDetectProxy::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this, &AutoDetectProxy::OnReadEvent);
+ socket_->SignalCloseEvent.connect(this, &AutoDetectProxy::OnCloseEvent);
+ socket_->Connect(proxy_.address);
+
+ // Timeout after 2 seconds
+ Thread::Current()->PostDelayed(2000, this, MSG_TIMEOUT);
+}
+
+void AutoDetectProxy::Complete(ProxyType type) {
+ Thread::Current()->Clear(this, MSG_TIMEOUT);
+ socket_->Close();
+
+ proxy_.type = type;
+ LoggingSeverity sev = (proxy_.type == PROXY_UNKNOWN) ? LS_ERROR : LS_INFO;
+ LOG_V(sev) << "AutoDetectProxy detected " << proxy_.address.ToString()
+ << " as type " << proxy_.type;
+
+ Thread::Current()->Quit();
+}
+
+void AutoDetectProxy::OnConnectEvent(AsyncSocket * socket) {
+ std::string probe;
+
+ switch (TEST_ORDER[next_]) {
+ case PROXY_HTTPS:
+ probe.assign("CONNECT www.google.com:443 HTTP/1.0\r\n"
+ "User-Agent: ");
+ probe.append(agent_);
+ probe.append("\r\n"
+ "Host: www.google.com\r\n"
+ "Content-Length: 0\r\n"
+ "Proxy-Connection: Keep-Alive\r\n"
+ "\r\n");
+ break;
+ case PROXY_SOCKS5:
+ probe.assign("\005\001\000", 3);
+ break;
+ default:
+ ASSERT(false);
+ return;
+ }
+
+ LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_]
+ << " sending " << probe.size() << " bytes";
+ socket_->Send(probe.data(), probe.size());
+}
+
+void AutoDetectProxy::OnReadEvent(AsyncSocket * socket) {
+ char data[257];
+ int len = socket_->Recv(data, 256);
+ if (len > 0) {
+ data[len] = 0;
+ LOG(LS_VERBOSE) << "AutoDetectProxy read " << len << " bytes";
+ }
+
+ switch (TEST_ORDER[next_]) {
+ case PROXY_HTTPS:
+ if ((len >= 2) && (data[0] == '\x05')) {
+ Complete(PROXY_SOCKS5);
+ return;
+ }
+ if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) {
+ Complete(PROXY_HTTPS);
+ return;
+ }
+ break;
+ case PROXY_SOCKS5:
+ if ((len >= 2) && (data[0] == '\x05')) {
+ Complete(PROXY_SOCKS5);
+ return;
+ }
+ break;
+ default:
+ ASSERT(false);
+ return;
+ }
+
+ ++next_;
+ Next();
+}
+
+void AutoDetectProxy::OnCloseEvent(AsyncSocket * socket, int error) {
+ LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error;
+ ++next_;
+ Next();
+}
+
+} // namespace talk_base
diff --git a/talk/base/autodetectproxy.h b/talk/base/autodetectproxy.h
new file mode 100644
index 0000000..6bb2a4b
--- /dev/null
+++ b/talk/base/autodetectproxy.h
@@ -0,0 +1,90 @@
+/*
+ * 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_AUTODETECTPROXY_H_
+#define TALK_BASE_AUTODETECTPROXY_H_
+
+#include <string>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// AutoDetectProxy
+///////////////////////////////////////////////////////////////////////////////
+
+class AsyncSocket;
+
+class AutoDetectProxy : public SignalThread {
+ public:
+ explicit AutoDetectProxy(const std::string& user_agent);
+
+ const ProxyInfo& proxy() const { return proxy_; }
+
+ void set_server_url(const std::string& url) {
+ server_url_ = url;
+ }
+ void set_proxy(const SocketAddress& proxy) {
+ proxy_.type = PROXY_UNKNOWN;
+ proxy_.address = proxy;
+ }
+ void set_auth_info(bool use_auth, const std::string& username,
+ const CryptString& password) {
+ if (use_auth) {
+ proxy_.username = username;
+ proxy_.password = password;
+ }
+ }
+
+ protected:
+ virtual ~AutoDetectProxy();
+
+ // SignalThread Interface
+ virtual void DoWork();
+ virtual void OnMessage(Message *msg);
+
+ void Next();
+ void Complete(ProxyType type);
+
+ void OnConnectEvent(AsyncSocket * socket);
+ void OnReadEvent(AsyncSocket * socket);
+ void OnCloseEvent(AsyncSocket * socket, int error);
+
+ private:
+ std::string agent_;
+ std::string server_url_;
+ ProxyInfo proxy_;
+ AsyncSocket* socket_;
+ int next_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_AUTODETECTPROXY_H_
diff --git a/talk/base/base64.cc b/talk/base/base64.cc
new file mode 100644
index 0000000..363c378
--- /dev/null
+++ b/talk/base/base64.cc
@@ -0,0 +1,243 @@
+
+//*********************************************************************
+//* Base64 - a simple base64 encoder and decoder.
+//*
+//* Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*
+//* Enhancements by Stanley Yamane:
+//* o reverse lookup table for the decode function
+//* o reserve string buffer space in advance
+//*
+//*********************************************************************
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+
+using std::string;
+using std::vector;
+
+namespace talk_base {
+
+static const char kPad = '=';
+static const unsigned char pd = 0xFD; // Padding
+static const unsigned char sp = 0xFE; // Whitespace
+static const unsigned char il = 0xFF; // Illegal base64 character
+
+const string Base64::Base64Table(
+// 0000000000111111111122222222223333333333444444444455555555556666
+// 0123456789012345678901234567890123456789012345678901234567890123
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
+
+// Decode Table gives the index of any valid base64 character in the
+// Base64 table
+// 65 == A, 97 == a, 48 == 0, 43 == +, 47 == /
+
+const unsigned char Base64::DecodeTable[] = {
+// 0 1 2 3 4 5 6 7 8 9
+ il,il,il,il,il,il,il,il,il,sp, // 0 - 9
+ sp,sp,sp,sp,il,il,il,il,il,il, // 10 - 19
+ il,il,il,il,il,il,il,il,il,il, // 20 - 29
+ il,il,sp,il,il,il,il,il,il,il, // 30 - 39
+ il,il,il,62,il,il,il,63,52,53, // 40 - 49
+ 54,55,56,57,58,59,60,61,il,il, // 50 - 59
+ il,pd,il,il,il, 0, 1, 2, 3, 4, // 60 - 69
+ 5, 6, 7, 8, 9,10,11,12,13,14, // 70 - 79
+ 15,16,17,18,19,20,21,22,23,24, // 80 - 89
+ 25,il,il,il,il,il,il,26,27,28, // 90 - 99
+ 29,30,31,32,33,34,35,36,37,38, // 100 - 109
+ 39,40,41,42,43,44,45,46,47,48, // 110 - 119
+ 49,50,51,il,il,il,il,il,il,il, // 120 - 129
+ il,il,il,il,il,il,il,il,il,il, // 130 - 139
+ il,il,il,il,il,il,il,il,il,il, // 140 - 149
+ il,il,il,il,il,il,il,il,il,il, // 150 - 159
+ il,il,il,il,il,il,il,il,il,il, // 160 - 169
+ il,il,il,il,il,il,il,il,il,il, // 170 - 179
+ il,il,il,il,il,il,il,il,il,il, // 180 - 189
+ il,il,il,il,il,il,il,il,il,il, // 190 - 199
+ il,il,il,il,il,il,il,il,il,il, // 200 - 209
+ il,il,il,il,il,il,il,il,il,il, // 210 - 219
+ il,il,il,il,il,il,il,il,il,il, // 220 - 229
+ il,il,il,il,il,il,il,il,il,il, // 230 - 239
+ il,il,il,il,il,il,il,il,il,il, // 240 - 249
+ il,il,il,il,il,il // 250 - 255
+};
+
+bool Base64::IsBase64Char(char ch) {
+ return (('A' <= ch) && (ch <= 'Z')) ||
+ (('a' <= ch) && (ch <= 'z')) ||
+ (('0' <= ch) && (ch <= '9')) ||
+ (ch == '+') || (ch == '/');
+}
+
+bool Base64::IsBase64Encoded(const std::string& str) {
+ for (size_t i = 0; i < str.size(); ++i) {
+ if (!IsBase64Char(str.at(i)))
+ return false;
+ }
+ return true;
+}
+
+void Base64::EncodeFromArray(const void* data, size_t len, string* result) {
+ ASSERT(NULL != result);
+ result->clear();
+ result->reserve(((len + 2) / 3) * 4);
+ const unsigned char* byte_data = static_cast<const unsigned char*>(data);
+
+ unsigned char c;
+ size_t i = 0;
+ while (i < len) {
+ c = (byte_data[i] >> 2) & 0x3f;
+ result->push_back(Base64Table[c]);
+
+ c = (byte_data[i] << 4) & 0x3f;
+ if (++i < len) {
+ c |= (byte_data[i] >> 4) & 0x0f;
+ }
+ result->push_back(Base64Table[c]);
+
+ if (i < len) {
+ c = (byte_data[i] << 2) & 0x3f;
+ if (++i < len) {
+ c |= (byte_data[i] >> 6) & 0x03;
+ }
+ result->push_back(Base64Table[c]);
+ } else {
+ result->push_back(kPad);
+ }
+
+ if (i < len) {
+ c = byte_data[i] & 0x3f;
+ result->push_back(Base64Table[c]);
+ ++i;
+ } else {
+ result->push_back(kPad);
+ }
+ }
+}
+
+size_t Base64::GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+ const char* data, size_t len, size_t* dpos,
+ unsigned char qbuf[4], bool* padded)
+{
+ size_t byte_len = 0, pad_len = 0, pad_start = 0;
+ for (; (byte_len < 4) && (*dpos < len); ++*dpos) {
+ qbuf[byte_len] = DecodeTable[static_cast<unsigned char>(data[*dpos])];
+ if ((il == qbuf[byte_len]) || (illegal_pads && (pd == qbuf[byte_len]))) {
+ if (parse_flags != DO_PARSE_ANY)
+ break;
+ // Ignore illegal characters
+ } else if (sp == qbuf[byte_len]) {
+ if (parse_flags == DO_PARSE_STRICT)
+ break;
+ // Ignore spaces
+ } else if (pd == qbuf[byte_len]) {
+ if (byte_len < 2) {
+ if (parse_flags != DO_PARSE_ANY)
+ break;
+ // Ignore unexpected padding
+ } else if (byte_len + pad_len >= 4) {
+ if (parse_flags != DO_PARSE_ANY)
+ break;
+ // Ignore extra pads
+ } else {
+ if (1 == ++pad_len) {
+ pad_start = *dpos;
+ }
+ }
+ } else {
+ if (pad_len > 0) {
+ if (parse_flags != DO_PARSE_ANY)
+ break;
+ // Ignore pads which are followed by data
+ pad_len = 0;
+ }
+ ++byte_len;
+ }
+ }
+ for (size_t i = byte_len; i < 4; ++i) {
+ qbuf[i] = 0;
+ }
+ if (4 == byte_len + pad_len) {
+ *padded = true;
+ } else {
+ *padded = false;
+ if (pad_len) {
+ // Roll back illegal padding
+ *dpos = pad_start;
+ }
+ }
+ return byte_len;
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+ string* result, size_t* data_used) {
+ return DecodeFromArrayTemplate<string>(data, len, flags, result, data_used);
+}
+
+bool Base64::DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+ vector<char>* result, size_t* data_used) {
+ return DecodeFromArrayTemplate<vector<char> >(data, len, flags, result,
+ data_used);
+}
+
+template<typename T>
+bool Base64::DecodeFromArrayTemplate(const char* data, size_t len,
+ DecodeFlags flags, T* result,
+ size_t* data_used)
+{
+ ASSERT(NULL != result);
+ ASSERT(flags <= (DO_PARSE_MASK | DO_PAD_MASK | DO_TERM_MASK));
+
+ const DecodeFlags parse_flags = flags & DO_PARSE_MASK;
+ const DecodeFlags pad_flags = flags & DO_PAD_MASK;
+ const DecodeFlags term_flags = flags & DO_TERM_MASK;
+ ASSERT(0 != parse_flags);
+ ASSERT(0 != pad_flags);
+ ASSERT(0 != term_flags);
+
+ result->clear();
+ result->reserve(len);
+
+ size_t dpos = 0;
+ bool success = true, padded;
+ unsigned char c, qbuf[4];
+ while (dpos < len) {
+ size_t qlen = GetNextQuantum(parse_flags, (DO_PAD_NO == pad_flags),
+ data, len, &dpos, qbuf, &padded);
+ c = (qbuf[0] << 2) | ((qbuf[1] >> 4) & 0x3);
+ if (qlen >= 2) {
+ result->push_back(c);
+ c = ((qbuf[1] << 4) & 0xf0) | ((qbuf[2] >> 2) & 0xf);
+ if (qlen >= 3) {
+ result->push_back(c);
+ c = ((qbuf[2] << 6) & 0xc0) | qbuf[3];
+ if (qlen >= 4) {
+ result->push_back(c);
+ c = 0;
+ }
+ }
+ }
+ if (qlen < 4) {
+ if ((DO_TERM_ANY != term_flags) && (0 != c)) {
+ success = false; // unused bits
+ }
+ if ((DO_PAD_YES == pad_flags) && !padded) {
+ success = false; // expected padding
+ }
+ break;
+ }
+ }
+ if ((DO_TERM_BUFFER == term_flags) && (dpos != len)) {
+ success = false; // unused chars
+ }
+ if (data_used) {
+ *data_used = dpos;
+ }
+ return success;
+}
+
+} // namespace talk_base
diff --git a/talk/base/base64.h b/talk/base/base64.h
new file mode 100644
index 0000000..73904dc
--- /dev/null
+++ b/talk/base/base64.h
@@ -0,0 +1,96 @@
+
+//*********************************************************************
+//* C_Base64 - a simple base64 encoder and decoder.
+//*
+//* Copyright (c) 1999, Bob Withers - bwit@pobox.com
+//*
+//* This code may be freely used for any purpose, either personal
+//* or commercial, provided the authors copyright notice remains
+//* intact.
+//*********************************************************************
+
+#ifndef TALK_BASE_BASE64_H__
+#define TALK_BASE_BASE64_H__
+
+#include <string>
+#include <vector>
+
+namespace talk_base {
+
+class Base64
+{
+public:
+ enum DecodeOption {
+ DO_PARSE_STRICT = 1, // Parse only base64 characters
+ DO_PARSE_WHITE = 2, // Parse only base64 and whitespace characters
+ DO_PARSE_ANY = 3, // Parse all characters
+ DO_PARSE_MASK = 3,
+
+ DO_PAD_YES = 4, // Padding is required
+ DO_PAD_ANY = 8, // Padding is optional
+ DO_PAD_NO = 12, // Padding is disallowed
+ DO_PAD_MASK = 12,
+
+ DO_TERM_BUFFER = 16, // Must termiante at end of buffer
+ DO_TERM_CHAR = 32, // May terminate at any character boundary
+ DO_TERM_ANY = 48, // May terminate at a sub-character bit offset
+ DO_TERM_MASK = 48,
+
+ // Strictest interpretation
+ DO_STRICT = DO_PARSE_STRICT | DO_PAD_YES | DO_TERM_BUFFER,
+
+ DO_LAX = DO_PARSE_ANY | DO_PAD_ANY | DO_TERM_CHAR,
+ };
+ typedef int DecodeFlags;
+
+ static bool IsBase64Char(char ch);
+
+ // Determines whether the given string consists entirely of valid base64
+ // encoded characters.
+ static bool IsBase64Encoded(const std::string& str);
+
+ static void EncodeFromArray(const void* data, size_t len,
+ std::string* result);
+ static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+ std::string* result, size_t* data_used);
+ static bool DecodeFromArray(const char* data, size_t len, DecodeFlags flags,
+ std::vector<char>* result, size_t* data_used);
+
+ // Convenience Methods
+ static inline std::string Encode(const std::string& data) {
+ std::string result;
+ EncodeFromArray(data.data(), data.size(), &result);
+ return result;
+ }
+ static inline std::string Decode(const std::string& data, DecodeFlags flags) {
+ std::string result;
+ DecodeFromArray(data.data(), data.size(), flags, &result, NULL);
+ return result;
+ }
+ static inline bool Decode(const std::string& data, DecodeFlags flags,
+ std::string* result, size_t* data_used)
+ {
+ return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+ }
+ static inline bool Decode(const std::string& data, DecodeFlags flags,
+ std::vector<char>* result, size_t* data_used)
+ {
+ return DecodeFromArray(data.data(), data.size(), flags, result, data_used);
+ }
+
+private:
+ static const std::string Base64Table;
+ static const unsigned char DecodeTable[];
+
+ static size_t GetNextQuantum(DecodeFlags parse_flags, bool illegal_pads,
+ const char* data, size_t len, size_t* dpos,
+ unsigned char qbuf[4], bool* padded);
+ template<typename T>
+ static bool DecodeFromArrayTemplate(const char* data, size_t len,
+ DecodeFlags flags, T* result,
+ size_t* data_used);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BASE64_H__
diff --git a/talk/base/basicdefs.h b/talk/base/basicdefs.h
new file mode 100644
index 0000000..0e58bf6
--- /dev/null
+++ b/talk/base/basicdefs.h
@@ -0,0 +1,37 @@
+/*
+ * 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_BASICDEFS_H__
+#define TALK_BASE_BASICDEFS_H__
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#define ARRAY_SIZE(x) (static_cast<int>((sizeof(x)/sizeof(x[0]))))
+
+#endif // TAKL_BASE_BASICDEFS_H__
diff --git a/talk/base/basicpacketsocketfactory.cc b/talk/base/basicpacketsocketfactory.cc
new file mode 100644
index 0000000..20173d1
--- /dev/null
+++ b/talk/base/basicpacketsocketfactory.cc
@@ -0,0 +1,159 @@
+/*
+ * 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/base/basicpacketsocketfactory.h"
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+ Thread* thread)
+ : thread_(thread),
+ socket_factory_(NULL) {
+}
+
+BasicPacketSocketFactory::BasicPacketSocketFactory(
+ SocketFactory* socket_factory)
+ : thread_(NULL),
+ socket_factory_(socket_factory) {
+}
+
+BasicPacketSocketFactory::~BasicPacketSocketFactory() {
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) {
+ // UDP sockets are simple.
+ talk_base::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(SOCK_DGRAM);
+ if (!socket) {
+ return NULL;
+ }
+ if (BindSocket(socket, address, min_port, max_port) < 0) {
+ LOG(LS_ERROR) << "UDP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+ return new talk_base::AsyncUDPSocket(socket);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ bool listen, bool ssl) {
+ talk_base::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(SOCK_STREAM);
+ if (!socket) {
+ return NULL;
+ }
+
+ if (BindSocket(socket, local_address, min_port, max_port) < 0) {
+ LOG(LS_ERROR) << "TCP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+ if (ssl) {
+ socket = new talk_base::AsyncSSLSocket(socket);
+ }
+
+ return new talk_base::AsyncTCPSocket(socket, true);
+}
+
+AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl) {
+ talk_base::AsyncSocket* socket =
+ socket_factory()->CreateAsyncSocket(SOCK_STREAM);
+ if (!socket) {
+ return NULL;
+ }
+
+ if (BindSocket(socket, local_address, 0, 0) < 0) {
+ LOG(LS_ERROR) << "TCP bind failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // If using a proxy, wrap the socket in a proxy socket.
+ if (proxy_info.type == talk_base::PROXY_SOCKS5) {
+ socket = new talk_base::AsyncSocksProxySocket(
+ socket, proxy_info.address, proxy_info.username, proxy_info.password);
+ } else if (proxy_info.type == talk_base::PROXY_HTTPS) {
+ socket = new talk_base::AsyncHttpsProxySocket(
+ socket, user_agent, proxy_info.address,
+ proxy_info.username, proxy_info.password);
+ }
+
+ // If using SSLTCP, wrap the TCP socket in a pseudo-SSL socket.
+ if (ssl) {
+ socket = new talk_base::AsyncSSLSocket(socket);
+ }
+
+ if (socket->Connect(remote_address) < 0) {
+ LOG(LS_ERROR) << "TCP connect failed with error "
+ << socket->GetError();
+ delete socket;
+ return NULL;
+ }
+
+ // Finally, wrap that socket in a TCP packet socket.
+ return new talk_base::AsyncTCPSocket(socket, false);
+}
+
+int BasicPacketSocketFactory::BindSocket(
+ AsyncSocket* socket, const SocketAddress& local_address,
+ int min_port, int max_port) {
+ int ret = -1;
+ if (min_port == 0 && max_port == 0) {
+ // If there's no port range, let the OS pick a port for us.
+ ret = socket->Bind(local_address);
+ } else {
+ // Otherwise, try to find a port in the provided range.
+ for (int port = min_port; ret < 0 && port <= max_port; ++port) {
+ ret = socket->Bind(talk_base::SocketAddress(local_address.ip(), port));
+ }
+ }
+ return ret;
+}
+
+SocketFactory* BasicPacketSocketFactory::socket_factory() {
+ if (thread_)
+ return thread_->socketserver();
+ else
+ return socket_factory_;
+}
+
+} // namespace talk_base
diff --git a/talk/base/basicpacketsocketfactory.h b/talk/base/basicpacketsocketfactory.h
new file mode 100644
index 0000000..2f39d07
--- /dev/null
+++ b/talk/base/basicpacketsocketfactory.h
@@ -0,0 +1,67 @@
+/*
+ * 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_BASE_BASICPACKETSOCKETFACTORY_H_
+#define TALK_BASE_BASICPACKETSOCKETFACTORY_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/packetsocketfactory.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class SocketFactory;
+class Thread;
+
+class BasicPacketSocketFactory : public PacketSocketFactory {
+ public:
+ explicit BasicPacketSocketFactory(Thread* thread);
+ explicit BasicPacketSocketFactory(SocketFactory* socket_factory);
+ virtual ~BasicPacketSocketFactory();
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& local_address, int min_port, int max_port);
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ bool listen, bool ssl);
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl);
+
+ private:
+ int BindSocket(AsyncSocket* socket, const SocketAddress& local_address,
+ int min_port, int max_port);
+
+ SocketFactory* socket_factory();
+
+ Thread* thread_;
+ SocketFactory* socket_factory_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BASICPACKETSOCKETFACTORY_H_
diff --git a/talk/base/basictypes.h b/talk/base/basictypes.h
new file mode 100644
index 0000000..a26a3ff
--- /dev/null
+++ b/talk/base/basictypes.h
@@ -0,0 +1,122 @@
+/*
+ * 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_BASE_BASICTYPES_H__
+#define TALK_BASE_BASICTYPES_H__
+
+#ifndef WIN32
+#include <stdint.h> // for uintptr_t
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "talk/base/constructormagic.h"
+
+#ifndef INT_TYPES_DEFINED
+#define INT_TYPES_DEFINED
+#ifdef COMPILER_MSVC
+typedef __int64 int64;
+#else
+typedef long long int64;
+#endif /* COMPILER_MSVC */
+typedef int int32;
+typedef short int16;
+typedef char int8;
+
+#ifdef COMPILER_MSVC
+typedef unsigned __int64 uint64;
+typedef __int64 int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## I64
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## UI64
+#endif
+#define INT64_F "I64"
+#else
+typedef unsigned long long uint64;
+typedef long long int64;
+#ifndef INT64_C
+#define INT64_C(x) x ## LL
+#endif
+#ifndef UINT64_C
+#define UINT64_C(x) x ## ULL
+#endif
+#define INT64_F "ll"
+#endif /* COMPILER_MSVC */
+typedef unsigned int uint32;
+typedef unsigned short uint16;
+typedef unsigned char uint8;
+#endif // INT_TYPES_DEFINED
+
+#ifdef WIN32
+typedef int socklen_t;
+#endif
+
+namespace talk_base {
+ template<class T> inline T _min(T a, T b) { return (a > b) ? b : a; }
+ template<class T> inline T _max(T a, T b) { return (a < b) ? b : a; }
+
+ // For wait functions that take a number of milliseconds, kForever indicates
+ // unlimited time.
+ const int kForever = -1;
+}
+
+// Detect compiler is for x86 or x64.
+#if defined(__x86_64__) || defined(_M_X64) || \
+ defined(__i386__) || defined(_M_IX86)
+#define CPU_X86 1
+#endif
+
+#ifdef WIN32
+#define alignof(t) __alignof(t)
+#else // !WIN32
+#define alignof(t) __alignof__(t)
+#endif // !WIN32
+#define IS_ALIGNED(p, a) (0==(reinterpret_cast<uintptr_t>(p) & ((a)-1)))
+#define ALIGNP(p, t) \
+ (reinterpret_cast<uint8*>(((reinterpret_cast<uintptr_t>(p) + \
+ ((t)-1)) & ~((t)-1))))
+
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void *>(&x))
+#define UNUSED2(x,y) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y))
+#define UNUSED3(x,y,z) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z))
+#define UNUSED4(x,y,z,a) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a))
+#define UNUSED5(x,y,z,a,b) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a)); Unused(static_cast<const void *>(&b))
+inline void Unused(const void *) { }
+#endif // UNUSED
+
+#if defined(__GNUC__)
+#define GCC_ATTR(x) __attribute__ ((x))
+#else // !__GNUC__
+#define GCC_ATTR(x)
+#endif // !__GNUC__
+
+#endif // TALK_BASE_BASICTYPES_H__
diff --git a/talk/base/buffer.h b/talk/base/buffer.h
new file mode 100644
index 0000000..311cfad
--- /dev/null
+++ b/talk/base/buffer.h
@@ -0,0 +1,119 @@
+/*
+ * libjingle
+ * Copyright 2004-2010, 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_BUFFER_H_
+#define TALK_BASE_BUFFER_H_
+
+#include <cstring>
+
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+// Basic buffer class, can be grown and shrunk dynamically.
+// Unlike std::string/vector, does not initialize data when expanding capacity.
+class Buffer {
+ public:
+ Buffer() {
+ Construct(NULL, 0, 0);
+ }
+ Buffer(const void* data, size_t length) {
+ Construct(data, length, length);
+ }
+ Buffer(const void* data, size_t length, size_t capacity) {
+ Construct(data, length, capacity);
+ }
+ Buffer(const Buffer& buf) {
+ Construct(buf.data(), buf.length(), buf.length());
+ }
+
+ const char* data() const { return data_.get(); }
+ char* data() { return data_.get(); }
+ // TODO: should this be size(), like STL?
+ size_t length() const { return length_; }
+ size_t capacity() const { return capacity_; }
+
+ Buffer& operator=(const Buffer& buf) {
+ if (&buf != this) {
+ Construct(buf.data(), buf.length(), buf.length());
+ }
+ return *this;
+ }
+ bool operator==(const Buffer& buf) const {
+ return (length_ == buf.length() &&
+ memcmp(data_.get(), buf.data(), length_) == 0);
+ }
+ bool operator!=(const Buffer& buf) const {
+ return !operator==(buf);
+ }
+
+ void SetData(const void* data, size_t length) {
+ ASSERT(data != NULL || length == 0);
+ SetLength(length);
+ memcpy(data_.get(), data, length);
+ }
+ void AppendData(const void* data, size_t length) {
+ ASSERT(data != NULL || length == 0);
+ size_t old_length = length_;
+ SetLength(length_ + length);
+ memcpy(data_.get() + old_length, data, length);
+ }
+ void SetLength(size_t length) {
+ SetCapacity(length);
+ length_ = length;
+ }
+ void SetCapacity(size_t capacity) {
+ if (capacity > capacity_) {
+ talk_base::scoped_array<char> data(new char[capacity]);
+ memcpy(data.get(), data_.get(), length_);
+ data_.swap(data);
+ capacity_ = capacity;
+ }
+ }
+
+ void TransferTo(Buffer* buf) {
+ ASSERT(buf != NULL);
+ buf->data_.reset(data_.release());
+ buf->length_ = length_;
+ buf->capacity_ = capacity_;
+ Construct(NULL, 0, 0);
+ }
+
+ protected:
+ void Construct(const void* data, size_t length, size_t capacity) {
+ data_.reset(new char[capacity_ = capacity]);
+ SetData(data, length);
+ }
+
+ scoped_array<char> data_;
+ size_t length_;
+ size_t capacity_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BUFFER_H_
diff --git a/talk/base/bytebuffer.cc b/talk/base/bytebuffer.cc
new file mode 100644
index 0000000..9aef159
--- /dev/null
+++ b/talk/base/bytebuffer.cc
@@ -0,0 +1,211 @@
+/*
+ * 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 "talk/base/bytebuffer.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+
+namespace talk_base {
+
+static const int DEFAULT_SIZE = 4096;
+
+ByteBuffer::ByteBuffer() {
+ start_ = 0;
+ end_ = 0;
+ size_ = DEFAULT_SIZE;
+ bytes_ = new char[size_];
+}
+
+ByteBuffer::ByteBuffer(const char* bytes, size_t len) {
+ start_ = 0;
+ end_ = len;
+ size_ = len;
+ bytes_ = new char[size_];
+ memcpy(bytes_, bytes, end_);
+}
+
+ByteBuffer::ByteBuffer(const char* bytes) {
+ start_ = 0;
+ end_ = strlen(bytes);
+ size_ = end_;
+ bytes_ = new char[size_];
+ memcpy(bytes_, bytes, end_);
+}
+
+ByteBuffer::~ByteBuffer() {
+ delete[] bytes_;
+}
+
+bool ByteBuffer::ReadUInt8(uint8* val) {
+ if (!val) return false;
+
+ return ReadBytes(reinterpret_cast<char*>(val), 1);
+}
+
+bool ByteBuffer::ReadUInt16(uint16* val) {
+ if (!val) return false;
+
+ uint16 v;
+ if (!ReadBytes(reinterpret_cast<char*>(&v), 2)) {
+ return false;
+ } else {
+ *val = NetworkToHost16(v);
+ return true;
+ }
+}
+
+bool ByteBuffer::ReadUInt24(uint32* val) {
+ if (!val) return false;
+
+ uint32 v = 0;
+ if (!ReadBytes(reinterpret_cast<char*>(&v) + 1, 3)) {
+ return false;
+ } else {
+ *val = NetworkToHost32(v);
+ return true;
+ }
+}
+
+bool ByteBuffer::ReadUInt32(uint32* val) {
+ if (!val) return false;
+
+ uint32 v;
+ if (!ReadBytes(reinterpret_cast<char*>(&v), 4)) {
+ return false;
+ } else {
+ *val = NetworkToHost32(v);
+ return true;
+ }
+}
+
+bool ByteBuffer::ReadUInt64(uint64* val) {
+ if (!val) return false;
+
+ uint64 v;
+ if (!ReadBytes(reinterpret_cast<char*>(&v), 8)) {
+ return false;
+ } else {
+ *val = NetworkToHost64(v);
+ return true;
+ }
+}
+
+bool ByteBuffer::ReadString(std::string* val, size_t len) {
+ if (!val) return false;
+
+ if (len > Length()) {
+ return false;
+ } else {
+ val->append(bytes_ + start_, len);
+ start_ += len;
+ return true;
+ }
+}
+
+bool ByteBuffer::ReadBytes(char* val, size_t len) {
+ if (len > Length()) {
+ return false;
+ } else {
+ memcpy(val, bytes_ + start_, len);
+ start_ += len;
+ return true;
+ }
+}
+
+void ByteBuffer::WriteUInt8(uint8 val) {
+ WriteBytes(reinterpret_cast<const char*>(&val), 1);
+}
+
+void ByteBuffer::WriteUInt16(uint16 val) {
+ uint16 v = HostToNetwork16(val);
+ WriteBytes(reinterpret_cast<const char*>(&v), 2);
+}
+
+void ByteBuffer::WriteUInt24(uint32 val) {
+ uint32 v = HostToNetwork32(val);
+ WriteBytes(reinterpret_cast<const char*>(&v) + 1, 3);
+}
+
+void ByteBuffer::WriteUInt32(uint32 val) {
+ uint32 v = HostToNetwork32(val);
+ WriteBytes(reinterpret_cast<const char*>(&v), 4);
+}
+
+void ByteBuffer::WriteUInt64(uint64 val) {
+ uint64 v = HostToNetwork64(val);
+ WriteBytes(reinterpret_cast<const char*>(&v), 8);
+}
+
+void ByteBuffer::WriteString(const std::string& val) {
+ WriteBytes(val.c_str(), val.size());
+}
+
+void ByteBuffer::WriteBytes(const char* val, size_t len) {
+ if (Length() + len > Capacity())
+ Resize(Length() + len);
+
+ memcpy(bytes_ + end_, val, len);
+ end_ += len;
+}
+
+void ByteBuffer::Resize(size_t size) {
+ if (size > size_)
+ size = _max(size, 3 * size_ / 2);
+
+ size_t len = _min(end_ - start_, size);
+ char* new_bytes = new char[size];
+ memcpy(new_bytes, bytes_ + start_, len);
+ delete [] bytes_;
+
+ start_ = 0;
+ end_ = len;
+ size_ = size;
+ bytes_ = new_bytes;
+}
+
+void ByteBuffer::Consume(size_t size) {
+ if (size > Length())
+ return;
+
+ start_ += size;
+}
+
+void ByteBuffer::Shift(size_t size) {
+ if (size > Length())
+ return;
+
+ end_ = Length() - size;
+ memmove(bytes_, bytes_ + start_ + size, end_);
+ start_ = 0;
+}
+
+} // namespace talk_base
diff --git a/talk/base/bytebuffer.h b/talk/base/bytebuffer.h
new file mode 100644
index 0000000..bb162ec
--- /dev/null
+++ b/talk/base/bytebuffer.h
@@ -0,0 +1,82 @@
+/*
+ * 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_BYTEBUFFER_H_
+#define TALK_BASE_BYTEBUFFER_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/constructormagic.h"
+
+namespace talk_base {
+
+class ByteBuffer {
+ public:
+ ByteBuffer();
+ ByteBuffer(const char* bytes, size_t len);
+ explicit ByteBuffer(const char* bytes); // uses strlen
+ ~ByteBuffer();
+
+ const char* Data() const { return bytes_ + start_; }
+ size_t Length() const { return end_ - start_; }
+ size_t Capacity() const { return size_ - start_; }
+
+ bool ReadUInt8(uint8* val);
+ bool ReadUInt16(uint16* val);
+ bool ReadUInt24(uint32* val);
+ bool ReadUInt32(uint32* val);
+ bool ReadUInt64(uint64* val);
+ bool ReadString(std::string* val, size_t len); // append to val
+ bool ReadBytes(char* val, size_t len);
+
+ void WriteUInt8(uint8 val);
+ void WriteUInt16(uint16 val);
+ void WriteUInt24(uint32 val);
+ void WriteUInt32(uint32 val);
+ void WriteUInt64(uint64 val);
+ void WriteString(const std::string& val);
+ void WriteBytes(const char* val, size_t len);
+
+ void Resize(size_t size);
+ void Consume(size_t size);
+ void Shift(size_t size);
+
+ private:
+ char* bytes_;
+ size_t size_;
+ size_t start_;
+ size_t end_;
+
+ // There are sensible ways to define these, but they aren't needed in our code
+ // base.
+ DISALLOW_COPY_AND_ASSIGN(ByteBuffer);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BYTEBUFFER_H_
diff --git a/talk/base/byteorder.h b/talk/base/byteorder.h
new file mode 100644
index 0000000..08094b8
--- /dev/null
+++ b/talk/base/byteorder.h
@@ -0,0 +1,172 @@
+/*
+ * 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_BYTEORDER_H__
+#define TALK_BASE_BYTEORDER_H__
+
+#ifdef POSIX
+#include <arpa/inet.h>
+#endif
+
+#ifdef WIN32
+#include <winsock2.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Reading and writing of little and big-endian numbers from memory
+// TODO: Add HostEndian #defines (HE)
+// TODO: Consider NetworkEndian as synonym for BigEndian, for clarity in use.
+// TODO: Consider creating optimized versions, such as direct read/writes of
+// integers in host-endian format, when the platform supports it.
+
+inline void Set8(void* memory, size_t offset, uint8 v) {
+ static_cast<uint8*>(memory)[offset] = v;
+}
+inline uint8 Get8(const void* memory, size_t offset) {
+ return static_cast<const uint8*>(memory)[offset];
+}
+
+inline void SetBE16(void* memory, uint16 v) {
+ Set8(memory, 0, static_cast<uint8>(v >> 8));
+ Set8(memory, 1, static_cast<uint8>(v >> 0));
+}
+inline void SetBE32(void* memory, uint32 v) {
+ Set8(memory, 0, static_cast<uint8>(v >> 24));
+ Set8(memory, 1, static_cast<uint8>(v >> 16));
+ Set8(memory, 2, static_cast<uint8>(v >> 8));
+ Set8(memory, 3, static_cast<uint8>(v >> 0));
+}
+inline void SetBE64(void* memory, uint64 v) {
+ Set8(memory, 0, static_cast<uint8>(v >> 56));
+ Set8(memory, 1, static_cast<uint8>(v >> 48));
+ Set8(memory, 2, static_cast<uint8>(v >> 40));
+ Set8(memory, 3, static_cast<uint8>(v >> 32));
+ Set8(memory, 4, static_cast<uint8>(v >> 24));
+ Set8(memory, 5, static_cast<uint8>(v >> 16));
+ Set8(memory, 6, static_cast<uint8>(v >> 8));
+ Set8(memory, 7, static_cast<uint8>(v >> 0));
+}
+inline uint16 GetBE16(const void* memory) {
+ return (static_cast<uint16>(Get8(memory, 0)) << 8)
+ | (static_cast<uint16>(Get8(memory, 1)) << 0);
+}
+inline uint32 GetBE32(const void* memory) {
+ return (static_cast<uint32>(Get8(memory, 0)) << 24)
+ | (static_cast<uint32>(Get8(memory, 1)) << 16)
+ | (static_cast<uint32>(Get8(memory, 2)) << 8)
+ | (static_cast<uint32>(Get8(memory, 3)) << 0);
+}
+inline uint64 GetBE64(const void* memory) {
+ return (static_cast<uint64>(Get8(memory, 0)) << 56)
+ | (static_cast<uint64>(Get8(memory, 1)) << 48)
+ | (static_cast<uint64>(Get8(memory, 2)) << 40)
+ | (static_cast<uint64>(Get8(memory, 3)) << 32)
+ | (static_cast<uint64>(Get8(memory, 4)) << 24)
+ | (static_cast<uint64>(Get8(memory, 5)) << 16)
+ | (static_cast<uint64>(Get8(memory, 6)) << 8)
+ | (static_cast<uint64>(Get8(memory, 7)) << 0);
+}
+
+inline void SetLE16(void* memory, uint16 v) {
+ Set8(memory, 1, static_cast<uint8>(v >> 8));
+ Set8(memory, 0, static_cast<uint8>(v >> 0));
+}
+inline void SetLE32(void* memory, uint32 v) {
+ Set8(memory, 3, static_cast<uint8>(v >> 24));
+ Set8(memory, 2, static_cast<uint8>(v >> 16));
+ Set8(memory, 1, static_cast<uint8>(v >> 8));
+ Set8(memory, 0, static_cast<uint8>(v >> 0));
+}
+inline void SetLE64(void* memory, uint64 v) {
+ Set8(memory, 7, static_cast<uint8>(v >> 56));
+ Set8(memory, 6, static_cast<uint8>(v >> 48));
+ Set8(memory, 5, static_cast<uint8>(v >> 40));
+ Set8(memory, 4, static_cast<uint8>(v >> 32));
+ Set8(memory, 3, static_cast<uint8>(v >> 24));
+ Set8(memory, 2, static_cast<uint8>(v >> 16));
+ Set8(memory, 1, static_cast<uint8>(v >> 8));
+ Set8(memory, 0, static_cast<uint8>(v >> 0));
+}
+inline uint16 GetLE16(const void* memory) {
+ return (static_cast<uint16>(Get8(memory, 1)) << 8)
+ | (static_cast<uint16>(Get8(memory, 0)) << 0);
+}
+inline uint32 GetLE32(const void* memory) {
+ return (static_cast<uint32>(Get8(memory, 3)) << 24)
+ | (static_cast<uint32>(Get8(memory, 2)) << 16)
+ | (static_cast<uint32>(Get8(memory, 1)) << 8)
+ | (static_cast<uint32>(Get8(memory, 0)) << 0);
+}
+inline uint64 GetLE64(const void* memory) {
+ return (static_cast<uint64>(Get8(memory, 7)) << 56)
+ | (static_cast<uint64>(Get8(memory, 6)) << 48)
+ | (static_cast<uint64>(Get8(memory, 5)) << 40)
+ | (static_cast<uint64>(Get8(memory, 4)) << 32)
+ | (static_cast<uint64>(Get8(memory, 3)) << 24)
+ | (static_cast<uint64>(Get8(memory, 2)) << 16)
+ | (static_cast<uint64>(Get8(memory, 1)) << 8)
+ | (static_cast<uint64>(Get8(memory, 0)) << 0);
+}
+
+// Check if the current host is big endian.
+inline bool IsHostBigEndian() {
+ static const int number = 1;
+ return (0 == *reinterpret_cast<const char*>(&number));
+}
+
+inline uint16 HostToNetwork16(uint16 n) {
+ return htons(n);
+}
+
+inline uint32 HostToNetwork32(uint32 n) {
+ return htonl(n);
+}
+
+inline uint64 HostToNetwork64(uint64 n) {
+ // If the host is little endian, GetBE64 converts n to big network endian.
+ return IsHostBigEndian() ? n : GetBE64(&n);
+}
+
+inline uint16 NetworkToHost16(uint16 n) {
+ return ntohs(n);
+}
+
+inline uint32 NetworkToHost32(uint32 n) {
+ return ntohl(n);
+}
+
+inline uint64 NetworkToHost64(uint64 n) {
+ // If the host is little endian, GetBE64 converts n to little endian.
+ return IsHostBigEndian() ? n : GetBE64(&n);
+}
+
+} // namespace talk_base
+
+#endif // TALK_BASE_BYTEORDER_H__
diff --git a/talk/base/checks.cc b/talk/base/checks.cc
new file mode 100644
index 0000000..5466783
--- /dev/null
+++ b/talk/base/checks.cc
@@ -0,0 +1,47 @@
+/*
+ * libjingle
+ * Copyright 2006, 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 <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/logging.h"
+
+void Fatal(const char* file, int line, const char* format, ...) {
+ char msg[256];
+
+ va_list arguments;
+ va_start(arguments, format);
+ vsnprintf(msg, sizeof(msg), format, arguments);
+ va_end(arguments);
+
+ LOG(LS_ERROR) << "\n\n#\n# Fatal error in " << file
+ << ", line " << line << "\n#" << msg
+ << "\n#\n";
+ abort();
+}
diff --git a/talk/base/checks.h b/talk/base/checks.h
new file mode 100644
index 0000000..12adc25
--- /dev/null
+++ b/talk/base/checks.h
@@ -0,0 +1,44 @@
+/*
+ * libjingle
+ * Copyright 2006, 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.
+ */
+
+// This module contains some basic debugging facilities.
+
+
+#ifndef SHARED_COMMANDLINEFLAGS_CHECKS_H__
+#define SHARED_COMMANDLINEFLAGS_CHECKS_H__
+
+#include <string.h>
+
+// Prints an error message to stderr and aborts execution.
+void Fatal(const char* file, int line, const char* format, ...);
+
+
+// The UNREACHABLE macro is very useful during development.
+#define UNREACHABLE() \
+ Fatal(__FILE__, __LINE__, "unreachable code")
+
+#endif // SHARED_COMMANDLINEFLAGS_CHECKS_H__
diff --git a/talk/base/common.cc b/talk/base/common.cc
new file mode 100644
index 0000000..59e73d5
--- /dev/null
+++ b/talk/base/common.cc
@@ -0,0 +1,71 @@
+/*
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <memory.h>
+
+#if WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif // WIN32
+
+#if OSX
+#include <CoreServices/CoreServices.h>
+#endif // OSX
+
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+//////////////////////////////////////////////////////////////////////
+// Assertions
+//////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+void Break() {
+#if WIN32
+ ::DebugBreak();
+#elif OSX // !WIN32
+ ::Debugger();
+#else // !OSX && !WIN32
+#if _DEBUG_HAVE_BACKTRACE
+ OutputTrace();
+#endif
+ abort();
+#endif // !OSX && !WIN32
+}
+
+void LogAssert(const char * function, const char * file, int line,
+ const char * expression) {
+ // TODO - if we put hooks in here, we can do a lot fancier logging
+ LOG(LS_ERROR) << file << "(" << line << ")" << ": ASSERT FAILED: "
+ << expression << " @ " << function;
+}
+
+} // namespace talk_base
diff --git a/talk/base/common.h b/talk/base/common.h
new file mode 100644
index 0000000..ba9c5ae
--- /dev/null
+++ b/talk/base/common.h
@@ -0,0 +1,138 @@
+/*
+ * 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_COMMON_H_
+#define TALK_BASE_COMMON_H_
+
+#include "talk/base/constructormagic.h"
+
+#if defined(_MSC_VER)
+// warning C4355: 'this' : used in base member initializer list
+#pragma warning(disable:4355)
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// General Utilities
+//////////////////////////////////////////////////////////////////////
+
+#ifndef UNUSED
+#define UNUSED(x) Unused(static_cast<const void *>(&x))
+#define UNUSED2(x,y) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y))
+#define UNUSED3(x,y,z) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z))
+#define UNUSED4(x,y,z,a) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a))
+#define UNUSED5(x,y,z,a,b) Unused(static_cast<const void *>(&x)); Unused(static_cast<const void *>(&y)); Unused(static_cast<const void *>(&z)); Unused(static_cast<const void *>(&a)); Unused(static_cast<const void *>(&b))
+inline void Unused(const void *) { }
+#endif // UNUSED
+
+#ifndef WIN32
+#define strnicmp(x,y,n) strncasecmp(x,y,n)
+#define stricmp(x,y) strcasecmp(x,y)
+
+// TODO: Remove this. std::max should be used everywhere in the code.
+// NOMINMAX must be defined where we include <windows.h>.
+#define stdmax(x,y) std::max(x,y)
+#else
+#define stdmax(x,y) talk_base::_max(x,y)
+#endif
+
+
+#define ARRAY_SIZE(x) (static_cast<int>((sizeof(x)/sizeof(x[0]))))
+
+/////////////////////////////////////////////////////////////////////////////
+// Assertions
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef ENABLE_DEBUG
+#define ENABLE_DEBUG _DEBUG
+#endif // !defined(ENABLE_DEBUG)
+
+#if ENABLE_DEBUG
+
+namespace talk_base {
+
+// Break causes the debugger to stop executing, or the program to abort
+void Break();
+
+// LogAssert writes information about an assertion to the log
+void LogAssert(const char * function, const char * file, int line,
+ const char * expression);
+
+inline bool Assert(bool result, const char * function, const char * file,
+ int line, const char * expression) {
+ if (!result) {
+ LogAssert(function, file, line, expression);
+ Break();
+ return false;
+ }
+ return true;
+}
+
+} // namespace talk_base
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#define __FUNCTION__ ""
+#endif
+
+#ifndef ASSERT
+#define ASSERT(x) (void)talk_base::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x)
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::Assert((x),__FUNCTION__,__FILE__,__LINE__,#x)
+#endif
+
+#else // !ENABLE_DEBUG
+
+namespace talk_base {
+
+inline bool ImplicitCastToBool(bool result) { return result; }
+
+} // namespace talk_base
+
+#ifndef ASSERT
+#define ASSERT(x) (void)0
+#endif
+
+#ifndef VERIFY
+#define VERIFY(x) talk_base::ImplicitCastToBool(x)
+#endif
+
+#endif // !ENABLE_DEBUG
+
+#define COMPILE_TIME_ASSERT(expr) char CTA_UNIQUE_NAME[expr]
+#define CTA_UNIQUE_NAME MAKE_NAME(__LINE__)
+#define CTA_MAKE_NAME(line) MAKE_NAME2(line)
+#define CTA_MAKE_NAME2(line) constraint_ ## line
+
+#ifdef __GNUC__
+// Forces compiler to inline, even against its better judgement. Use wisely.
+#define FORCE_INLINE __attribute__((always_inline))
+#else
+#define FORCE_INLINE
+#endif
+
+#endif // TALK_BASE_COMMON_H_
diff --git a/talk/base/constructormagic.h b/talk/base/constructormagic.h
new file mode 100644
index 0000000..8b1f7ff
--- /dev/null
+++ b/talk/base/constructormagic.h
@@ -0,0 +1,55 @@
+/*
+ * 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_CONSTRUCTORMAGIC_H_
+#define TALK_BASE_CONSTRUCTORMAGIC_H_
+
+#define DISALLOW_ASSIGN(TypeName) \
+ void operator=(const TypeName&)
+
+// A macro to disallow the evil copy constructor and operator= functions
+// This should be used in the private: declarations for a class
+#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
+ TypeName(const TypeName&); \
+ DISALLOW_ASSIGN(TypeName)
+
+// Alternative, less-accurate legacy name.
+#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \
+ DISALLOW_COPY_AND_ASSIGN(TypeName)
+
+// A macro to disallow all the implicit constructors, namely the
+// default constructor, copy constructor and operator= functions.
+//
+// This should be used in the private: declarations for a class
+// that wants to prevent anyone from instantiating it. This is
+// especially useful for classes containing only static methods.
+#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \
+ TypeName(); \
+ DISALLOW_EVIL_CONSTRUCTORS(TypeName)
+
+
+#endif // TALK_BASE_CONSTRUCTORMAGIC_H_
diff --git a/talk/base/criticalsection.h b/talk/base/criticalsection.h
new file mode 100644
index 0000000..ee3db1a
--- /dev/null
+++ b/talk/base/criticalsection.h
@@ -0,0 +1,131 @@
+/*
+ * 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_CRITICALSECTION_H__
+#define TALK_BASE_CRITICALSECTION_H__
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+
+#ifdef _DEBUG
+#define CS_TRACK_OWNER 1
+#endif // _DEBUG
+
+#if CS_TRACK_OWNER
+#define TRACK_OWNER(x) x
+#else // !CS_TRACK_OWNER
+#define TRACK_OWNER(x)
+#endif // !CS_TRACK_OWNER
+
+namespace talk_base {
+
+#ifdef WIN32
+class CriticalSection {
+public:
+ CriticalSection() {
+ InitializeCriticalSection(&crit_);
+ // Windows docs say 0 is not a valid thread id
+ TRACK_OWNER(thread_ = 0);
+ }
+ ~CriticalSection() {
+ DeleteCriticalSection(&crit_);
+ }
+ void Enter() {
+ EnterCriticalSection(&crit_);
+ TRACK_OWNER(thread_ = GetCurrentThreadId());
+ }
+ void Leave() {
+ TRACK_OWNER(thread_ = 0);
+ LeaveCriticalSection(&crit_);
+ }
+
+#if CS_TRACK_OWNER
+ bool CurrentThreadIsOwner() const { return thread_ == GetCurrentThreadId(); }
+#endif // CS_TRACK_OWNER
+
+private:
+ CRITICAL_SECTION crit_;
+ TRACK_OWNER(DWORD thread_); // The section's owning thread id
+};
+#endif // WIN32
+
+#ifdef POSIX
+class CriticalSection {
+public:
+ CriticalSection() {
+ pthread_mutexattr_t mutex_attribute;
+ pthread_mutexattr_init(&mutex_attribute);
+ pthread_mutexattr_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutex_, &mutex_attribute);
+ pthread_mutexattr_destroy(&mutex_attribute);
+ TRACK_OWNER(thread_ = 0);
+ }
+ ~CriticalSection() {
+ pthread_mutex_destroy(&mutex_);
+ }
+ void Enter() {
+ pthread_mutex_lock(&mutex_);
+ TRACK_OWNER(thread_ = pthread_self());
+ }
+ void Leave() {
+ TRACK_OWNER(thread_ = 0);
+ pthread_mutex_unlock(&mutex_);
+ }
+
+#if CS_TRACK_OWNER
+ bool CurrentThreadIsOwner() const { return pthread_equal(thread_, pthread_self()); }
+#endif // CS_TRACK_OWNER
+
+private:
+ pthread_mutex_t mutex_;
+ TRACK_OWNER(pthread_t thread_);
+};
+#endif // POSIX
+
+// CritScope, for serializing exection through a scope
+
+class CritScope {
+public:
+ CritScope(CriticalSection *pcrit) {
+ pcrit_ = pcrit;
+ pcrit_->Enter();
+ }
+ ~CritScope() {
+ pcrit_->Leave();
+ }
+private:
+ CriticalSection *pcrit_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_CRITICALSECTION_H__
diff --git a/talk/base/cryptstring.h b/talk/base/cryptstring.h
new file mode 100644
index 0000000..eb39be2
--- /dev/null
+++ b/talk/base/cryptstring.h
@@ -0,0 +1,198 @@
+/*
+ * 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_CRYPTSTRING_H_
+#define _TALK_BASE_CRYPTSTRING_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+#include "talk/base/linked_ptr.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class CryptStringImpl {
+public:
+ virtual ~CryptStringImpl() {}
+ virtual size_t GetLength() const = 0;
+ virtual void CopyTo(char * dest, bool nullterminate) const = 0;
+ virtual std::string UrlEncode() const = 0;
+ virtual CryptStringImpl * Copy() const = 0;
+ virtual void CopyRawTo(std::vector<unsigned char> * dest) const = 0;
+};
+
+class EmptyCryptStringImpl : public CryptStringImpl {
+public:
+ virtual ~EmptyCryptStringImpl() {}
+ virtual size_t GetLength() const { return 0; }
+ virtual void CopyTo(char * dest, bool nullterminate) const {
+ if (nullterminate) {
+ *dest = '\0';
+ }
+ }
+ virtual std::string UrlEncode() const { return ""; }
+ virtual CryptStringImpl * Copy() const { return new EmptyCryptStringImpl(); }
+ virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+ dest->clear();
+ }
+};
+
+class CryptString {
+public:
+ CryptString() : impl_(new EmptyCryptStringImpl()) {}
+ size_t GetLength() const { return impl_->GetLength(); }
+ void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); }
+ CryptString(const CryptString & other) : impl_(other.impl_->Copy()) {}
+ explicit CryptString(const CryptStringImpl & impl) : impl_(impl.Copy()) {}
+ CryptString & operator=(const CryptString & other) {
+ if (this != &other) {
+ impl_.reset(other.impl_->Copy());
+ }
+ return *this;
+ }
+ void Clear() { impl_.reset(new EmptyCryptStringImpl()); }
+ std::string UrlEncode() const { return impl_->UrlEncode(); }
+ void CopyRawTo(std::vector<unsigned char> * dest) const {
+ return impl_->CopyRawTo(dest);
+ }
+
+private:
+ scoped_ptr<const CryptStringImpl> impl_;
+};
+
+
+// Used for constructing strings where a password is involved and we
+// need to ensure that we zero memory afterwards
+class FormatCryptString {
+public:
+ FormatCryptString() {
+ storage_ = new char[32];
+ capacity_ = 32;
+ length_ = 0;
+ storage_[0] = 0;
+ }
+
+ void Append(const std::string & text) {
+ Append(text.data(), text.length());
+ }
+
+ void Append(const char * data, size_t length) {
+ EnsureStorage(length_ + length + 1);
+ memcpy(storage_ + length_, data, length);
+ length_ += length;
+ storage_[length_] = '\0';
+ }
+
+ void Append(const CryptString * password) {
+ size_t len = password->GetLength();
+ EnsureStorage(length_ + len + 1);
+ password->CopyTo(storage_ + length_, true);
+ length_ += len;
+ }
+
+ size_t GetLength() {
+ return length_;
+ }
+
+ const char * GetData() {
+ return storage_;
+ }
+
+
+ // Ensures storage of at least n bytes
+ void EnsureStorage(size_t n) {
+ if (capacity_ >= n) {
+ return;
+ }
+
+ size_t old_capacity = capacity_;
+ char * old_storage = storage_;
+
+ for (;;) {
+ capacity_ *= 2;
+ if (capacity_ >= n)
+ break;
+ }
+
+ storage_ = new char[capacity_];
+
+ if (old_capacity) {
+ memcpy(storage_, old_storage, length_);
+
+ // zero memory in a way that an optimizer won't optimize it out
+ old_storage[0] = 0;
+ for (size_t i = 1; i < old_capacity; i++) {
+ old_storage[i] = old_storage[i - 1];
+ }
+ delete[] old_storage;
+ }
+ }
+
+ ~FormatCryptString() {
+ if (capacity_) {
+ storage_[0] = 0;
+ for (size_t i = 1; i < capacity_; i++) {
+ storage_[i] = storage_[i - 1];
+ }
+ }
+ delete[] storage_;
+ }
+private:
+ char * storage_;
+ size_t capacity_;
+ size_t length_;
+};
+
+class InsecureCryptStringImpl : public CryptStringImpl {
+ public:
+ std::string& password() { return password_; }
+ const std::string& password() const { return password_; }
+
+ virtual ~InsecureCryptStringImpl() {}
+ virtual size_t GetLength() const { return password_.size(); }
+ virtual void CopyTo(char * dest, bool nullterminate) const {
+ memcpy(dest, password_.data(), password_.size());
+ if (nullterminate) dest[password_.size()] = 0;
+ }
+ virtual std::string UrlEncode() const { return password_; }
+ virtual CryptStringImpl * Copy() const {
+ InsecureCryptStringImpl * copy = new InsecureCryptStringImpl;
+ copy->password() = password_;
+ return copy;
+ }
+ virtual void CopyRawTo(std::vector<unsigned char> * dest) const {
+ dest->resize(password_.size());
+ memcpy(&dest->front(), password_.data(), password_.size());
+ }
+ private:
+ std::string password_;
+};
+
+}
+
+#endif // _TALK_BASE_CRYPTSTRING_H_
diff --git a/talk/base/diskcache.cc b/talk/base/diskcache.cc
new file mode 100644
index 0000000..4e70543
--- /dev/null
+++ b/talk/base/diskcache.cc
@@ -0,0 +1,364 @@
+/*
+ * 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 <time.h>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef _DEBUG
+#define TRANSPARENT_CACHE_NAMES 1
+#else // !_DEBUG
+#define TRANSPARENT_CACHE_NAMES 0
+#endif // !_DEBUG
+
+namespace talk_base {
+
+class DiskCache;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCacheAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCacheAdapter : public StreamAdapterInterface {
+public:
+ DiskCacheAdapter(const DiskCache* cache, const std::string& id, size_t index,
+ StreamInterface* stream)
+ : StreamAdapterInterface(stream), cache_(cache), id_(id), index_(index)
+ { }
+ virtual ~DiskCacheAdapter() {
+ Close();
+ cache_->ReleaseResource(id_, index_);
+ }
+
+private:
+ const DiskCache* cache_;
+ std::string id_;
+ size_t index_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache
+///////////////////////////////////////////////////////////////////////////////
+
+DiskCache::DiskCache() : max_cache_(0), total_size_(0), total_accessors_(0) {
+}
+
+DiskCache::~DiskCache() {
+ ASSERT(0 == total_accessors_);
+}
+
+bool DiskCache::Initialize(const std::string& folder, size_t size) {
+ if (!folder_.empty() || !Filesystem::CreateFolder(folder))
+ return false;
+
+ folder_ = folder;
+ max_cache_ = size;
+ ASSERT(0 == total_size_);
+
+ if (!InitializeEntries())
+ return false;
+
+ return CheckLimit();
+}
+
+bool DiskCache::Purge() {
+ if (folder_.empty())
+ return false;
+
+ if (total_accessors_ > 0) {
+ LOG_F(LS_WARNING) << "Cache files open";
+ return false;
+ }
+
+ if (!PurgeFiles())
+ return false;
+
+ map_.clear();
+ return true;
+}
+
+bool DiskCache::LockResource(const std::string& id) {
+ Entry* entry = GetOrCreateEntry(id, true);
+ if (LS_LOCKED == entry->lock_state)
+ return false;
+ if ((LS_UNLOCKED == entry->lock_state) && (entry->accessors > 0))
+ return false;
+ if ((total_size_ > max_cache_) && !CheckLimit()) {
+ LOG_F(LS_WARNING) << "Cache overfull";
+ return false;
+ }
+ entry->lock_state = LS_LOCKED;
+ return true;
+}
+
+StreamInterface* DiskCache::WriteResource(const std::string& id, size_t index) {
+ Entry* entry = GetOrCreateEntry(id, false);
+ if (LS_LOCKED != entry->lock_state)
+ return NULL;
+
+ size_t previous_size = 0;
+ std::string filename(IdToFilename(id, index));
+ FileStream::GetSize(filename, &previous_size);
+ ASSERT(previous_size <= entry->size);
+ if (previous_size > entry->size) {
+ previous_size = entry->size;
+ }
+
+ scoped_ptr<FileStream> file(new FileStream);
+ if (!file->Open(filename, "wb")) {
+ LOG_F(LS_ERROR) << "Couldn't create cache file";
+ return NULL;
+ }
+
+ entry->streams = stdmax(entry->streams, index + 1);
+ entry->size -= previous_size;
+ total_size_ -= previous_size;
+
+ entry->accessors += 1;
+ total_accessors_ += 1;
+ return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::UnlockResource(const std::string& id) {
+ Entry* entry = GetOrCreateEntry(id, false);
+ if (LS_LOCKED != entry->lock_state)
+ return false;
+
+ if (entry->accessors > 0) {
+ entry->lock_state = LS_UNLOCKING;
+ } else {
+ entry->lock_state = LS_UNLOCKED;
+ entry->last_modified = time(0);
+ CheckLimit();
+ }
+ return true;
+}
+
+StreamInterface* DiskCache::ReadResource(const std::string& id,
+ size_t index) const {
+ const Entry* entry = GetEntry(id);
+ if (LS_UNLOCKED != entry->lock_state)
+ return NULL;
+ if (index >= entry->streams)
+ return NULL;
+
+ scoped_ptr<FileStream> file(new FileStream);
+ if (!file->Open(IdToFilename(id, index), "rb"))
+ return NULL;
+
+ entry->accessors += 1;
+ total_accessors_ += 1;
+ return new DiskCacheAdapter(this, id, index, file.release());
+}
+
+bool DiskCache::HasResource(const std::string& id) const {
+ const Entry* entry = GetEntry(id);
+ return (NULL != entry) && (entry->streams > 0);
+}
+
+bool DiskCache::HasResourceStream(const std::string& id, size_t index) const {
+ const Entry* entry = GetEntry(id);
+ if ((NULL == entry) || (index >= entry->streams))
+ return false;
+
+ std::string filename = IdToFilename(id, index);
+
+ return FileExists(filename);
+}
+
+bool DiskCache::DeleteResource(const std::string& id) {
+ Entry* entry = GetOrCreateEntry(id, false);
+ if (!entry)
+ return true;
+
+ if ((LS_UNLOCKED != entry->lock_state) || (entry->accessors > 0))
+ return false;
+
+ bool success = true;
+ for (size_t index = 0; index < entry->streams; ++index) {
+ std::string filename = IdToFilename(id, index);
+
+ if (!FileExists(filename))
+ continue;
+
+ if (!DeleteFile(filename)) {
+ LOG_F(LS_ERROR) << "Couldn't remove cache file: " << filename;
+ success = false;
+ }
+ }
+
+ total_size_ -= entry->size;
+ map_.erase(id);
+ return success;
+}
+
+bool DiskCache::CheckLimit() {
+#ifdef _DEBUG
+ // Temporary check to make sure everything is working correctly.
+ size_t cache_size = 0;
+ for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+ cache_size += it->second.size;
+ }
+ ASSERT(cache_size == total_size_);
+#endif // _DEBUG
+
+ // TODO: Replace this with a non-brain-dead algorithm for clearing out the
+ // oldest resources... something that isn't O(n^2)
+ while (total_size_ > max_cache_) {
+ EntryMap::iterator oldest = map_.end();
+ for (EntryMap::iterator it = map_.begin(); it != map_.end(); ++it) {
+ if ((LS_UNLOCKED != it->second.lock_state) || (it->second.accessors > 0))
+ continue;
+ oldest = it;
+ break;
+ }
+ if (oldest == map_.end()) {
+ LOG_F(LS_WARNING) << "All resources are locked!";
+ return false;
+ }
+ for (EntryMap::iterator it = oldest++; it != map_.end(); ++it) {
+ if (it->second.last_modified < oldest->second.last_modified) {
+ oldest = it;
+ }
+ }
+ if (!DeleteResource(oldest->first)) {
+ LOG_F(LS_ERROR) << "Couldn't delete from cache!";
+ return false;
+ }
+ }
+ return true;
+}
+
+std::string DiskCache::IdToFilename(const std::string& id, size_t index) const {
+#ifdef TRANSPARENT_CACHE_NAMES
+ // This escapes colons and other filesystem characters, so the user can't open
+ // special devices (like "COM1:"), or access other directories.
+ size_t buffer_size = id.length()*3 + 1;
+ char* buffer = new char[buffer_size];
+ encode(buffer, buffer_size, id.data(), id.length(),
+ unsafe_filename_characters(), '%');
+ // TODO: ASSERT(strlen(buffer) < FileSystem::MaxBasenameLength());
+#else // !TRANSPARENT_CACHE_NAMES
+ // We might want to just use a hash of the filename at some point, both for
+ // obfuscation, and to avoid both filename length and escaping issues.
+ ASSERT(false);
+#endif // !TRANSPARENT_CACHE_NAMES
+
+ char extension[32];
+ sprintfn(extension, ARRAY_SIZE(extension), ".%u", index);
+
+ Pathname pathname;
+ pathname.SetFolder(folder_);
+ pathname.SetBasename(buffer);
+ pathname.SetExtension(extension);
+
+#ifdef TRANSPARENT_CACHE_NAMES
+ delete [] buffer;
+#endif // TRANSPARENT_CACHE_NAMES
+
+ return pathname.pathname();
+}
+
+bool DiskCache::FilenameToId(const std::string& filename, std::string* id,
+ size_t* index) const {
+ Pathname pathname(filename);
+ unsigned tempdex;
+ if (1 != sscanf(pathname.extension().c_str(), ".%u", &tempdex))
+ return false;
+
+ *index = static_cast<size_t>(tempdex);
+
+ size_t buffer_size = pathname.basename().length() + 1;
+ char* buffer = new char[buffer_size];
+ decode(buffer, buffer_size, pathname.basename().data(),
+ pathname.basename().length(), '%');
+ id->assign(buffer);
+ delete [] buffer;
+ return true;
+}
+
+DiskCache::Entry* DiskCache::GetOrCreateEntry(const std::string& id,
+ bool create) {
+ EntryMap::iterator it = map_.find(id);
+ if (it != map_.end())
+ return &it->second;
+ if (!create)
+ return NULL;
+ Entry e;
+ e.lock_state = LS_UNLOCKED;
+ e.accessors = 0;
+ e.size = 0;
+ e.streams = 0;
+ e.last_modified = time(0);
+ it = map_.insert(EntryMap::value_type(id, e)).first;
+ return &it->second;
+}
+
+void DiskCache::ReleaseResource(const std::string& id, size_t index) const {
+ const Entry* entry = GetEntry(id);
+ if (!entry) {
+ LOG_F(LS_WARNING) << "Missing cache entry";
+ ASSERT(false);
+ return;
+ }
+
+ entry->accessors -= 1;
+ total_accessors_ -= 1;
+
+ if (LS_UNLOCKED != entry->lock_state) {
+ // This is safe, because locked resources only issue WriteResource, which
+ // is non-const. Think about a better way to handle it.
+ DiskCache* this2 = const_cast<DiskCache*>(this);
+ Entry* entry2 = this2->GetOrCreateEntry(id, false);
+
+ size_t new_size = 0;
+ std::string filename(IdToFilename(id, index));
+ FileStream::GetSize(filename, &new_size);
+ entry2->size += new_size;
+ this2->total_size_ += new_size;
+
+ if ((LS_UNLOCKING == entry->lock_state) && (0 == entry->accessors)) {
+ entry2->last_modified = time(0);
+ entry2->lock_state = LS_UNLOCKED;
+ this2->CheckLimit();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/diskcache.h b/talk/base/diskcache.h
new file mode 100644
index 0000000..c5a1dfc
--- /dev/null
+++ b/talk/base/diskcache.h
@@ -0,0 +1,142 @@
+/*
+ * 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_DISKCACHE_H__
+#define TALK_BASE_DISKCACHE_H__
+
+#include <map>
+#include <string>
+
+#ifdef WIN32
+#undef UnlockResource
+#endif // WIN32
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// DiskCache - An LRU cache of streams, stored on disk.
+//
+// Streams are identified by a unique resource id. Multiple streams can be
+// associated with each resource id, distinguished by an index. When old
+// resources are flushed from the cache, all streams associated with those
+// resources are removed together.
+// DiskCache is designed to persist across executions of the program. It is
+// safe for use from an arbitrary number of users on a single thread, but not
+// from multiple threads or other processes.
+///////////////////////////////////////////////////////////////////////////////
+
+class DiskCache {
+public:
+ DiskCache();
+ virtual ~DiskCache();
+
+ bool Initialize(const std::string& folder, size_t size);
+ bool Purge();
+
+ bool LockResource(const std::string& id);
+ StreamInterface* WriteResource(const std::string& id, size_t index);
+ bool UnlockResource(const std::string& id);
+
+ StreamInterface* ReadResource(const std::string& id, size_t index) const;
+
+ bool HasResource(const std::string& id) const;
+ bool HasResourceStream(const std::string& id, size_t index) const;
+ bool DeleteResource(const std::string& id);
+
+ protected:
+ virtual bool InitializeEntries() = 0;
+ virtual bool PurgeFiles() = 0;
+
+ virtual bool FileExists(const std::string& filename) const = 0;
+ virtual bool DeleteFile(const std::string& filename) const = 0;
+
+ enum LockState { LS_UNLOCKED, LS_LOCKED, LS_UNLOCKING };
+ struct Entry {
+ LockState lock_state;
+ mutable size_t accessors;
+ size_t size;
+ size_t streams;
+ time_t last_modified;
+ };
+ typedef std::map<std::string, Entry> EntryMap;
+ friend class DiskCacheAdapter;
+
+ bool CheckLimit();
+
+ std::string IdToFilename(const std::string& id, size_t index) const;
+ bool FilenameToId(const std::string& filename, std::string* id,
+ size_t* index) const;
+
+ const Entry* GetEntry(const std::string& id) const {
+ return const_cast<DiskCache*>(this)->GetOrCreateEntry(id, false);
+ }
+ Entry* GetOrCreateEntry(const std::string& id, bool create);
+
+ void ReleaseResource(const std::string& id, size_t index) const;
+
+ std::string folder_;
+ size_t max_cache_, total_size_;
+ EntryMap map_;
+ mutable size_t total_accessors_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// CacheLock - Automatically manage locking and unlocking, with optional
+// rollback semantics
+///////////////////////////////////////////////////////////////////////////////
+
+class CacheLock {
+public:
+ CacheLock(DiskCache* cache, const std::string& id, bool rollback = false)
+ : cache_(cache), id_(id), rollback_(rollback)
+ {
+ locked_ = cache_->LockResource(id_);
+ }
+ ~CacheLock() {
+ if (locked_) {
+ cache_->UnlockResource(id_);
+ if (rollback_) {
+ cache_->DeleteResource(id_);
+ }
+ }
+ }
+ bool IsLocked() const { return locked_; }
+ void Commit() { rollback_ = false; }
+
+private:
+ DiskCache* cache_;
+ std::string id_;
+ bool rollback_, locked_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_DISKCACHE_H__
diff --git a/talk/base/event.cc b/talk/base/event.cc
new file mode 100644
index 0000000..f2fd04d
--- /dev/null
+++ b/talk/base/event.cc
@@ -0,0 +1,193 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/event.h"
+
+#if defined(WIN32)
+#include <windows.h>
+#elif defined(POSIX)
+#include <pthread.h>
+#include <sys/time.h>
+#include <time.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+namespace talk_base {
+
+#if defined(WIN32)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+ : is_manual_reset_(manual_reset),
+ is_initially_signaled_(initially_signaled),
+ event_handle_(NULL) {
+}
+
+bool Event::EnsureInitialized() {
+ if (event_handle_ == NULL) {
+ event_handle_ = ::CreateEvent(NULL, // Security attributes.
+ is_manual_reset_,
+ is_initially_signaled_,
+ NULL); // Name.
+ }
+
+ return (event_handle_ != NULL);
+}
+
+Event::~Event() {
+ if (event_handle_ != NULL) {
+ CloseHandle(event_handle_);
+ event_handle_ = NULL;
+ }
+}
+
+bool Event::Set() {
+ if (!EnsureInitialized())
+ return false;
+
+ SetEvent(event_handle_);
+ return true;
+}
+
+bool Event::Reset() {
+ if (!EnsureInitialized())
+ return false;
+
+ ResetEvent(event_handle_);
+ return true;
+}
+
+bool Event::Wait(int cms) {
+ DWORD ms = (cms == kForever)? INFINITE : cms;
+
+ if (!EnsureInitialized())
+ return false;
+ else
+ return (WaitForSingleObject(event_handle_, ms) == WAIT_OBJECT_0);
+}
+
+#elif defined(POSIX)
+
+Event::Event(bool manual_reset, bool initially_signaled)
+ : is_manual_reset_(manual_reset),
+ event_status_(initially_signaled),
+ event_mutex_initialized_(false),
+ event_cond_initialized_(false) {
+}
+
+bool Event::EnsureInitialized() {
+ if (!event_mutex_initialized_) {
+ if (pthread_mutex_init(&event_mutex_, NULL) == 0)
+ event_mutex_initialized_ = true;
+ }
+
+ if (!event_cond_initialized_) {
+ if (pthread_cond_init(&event_cond_, NULL) == 0)
+ event_cond_initialized_ = true;
+ }
+
+ return (event_mutex_initialized_ && event_cond_initialized_);
+}
+
+Event::~Event() {
+ if (event_mutex_initialized_) {
+ pthread_mutex_destroy(&event_mutex_);
+ event_mutex_initialized_ = false;
+ }
+
+ if (event_cond_initialized_) {
+ pthread_cond_destroy(&event_cond_);
+ event_cond_initialized_ = false;
+ }
+}
+
+bool Event::Set() {
+ if (!EnsureInitialized())
+ return false;
+
+ pthread_mutex_lock(&event_mutex_);
+ event_status_ = true;
+ pthread_cond_broadcast(&event_cond_);
+ pthread_mutex_unlock(&event_mutex_);
+ return true;
+}
+
+bool Event::Reset() {
+ if (!EnsureInitialized())
+ return false;
+
+ pthread_mutex_lock(&event_mutex_);
+ event_status_ = false;
+ pthread_mutex_unlock(&event_mutex_);
+ return true;
+}
+
+bool Event::Wait(int cms) {
+ if (!EnsureInitialized())
+ return false;
+
+ pthread_mutex_lock(&event_mutex_);
+ int error = 0;
+
+ if (cms != kForever) {
+ // Converting from seconds and microseconds (1e-6) plus
+ // milliseconds (1e-3) to seconds and nanoseconds (1e-9).
+
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ struct timespec ts;
+ ts.tv_sec = tv.tv_sec + (cms / 1000);
+ ts.tv_nsec = tv.tv_usec * 1000 + (cms % 1000) * 1000000;
+
+ // Handle overflow.
+ if (ts.tv_nsec >= 1000000000) {
+ ts.tv_sec++;
+ ts.tv_nsec -= 1000000000;
+ }
+
+ while (!event_status_ && error == 0)
+ error = pthread_cond_timedwait(&event_cond_, &event_mutex_, &ts);
+ } else {
+ while (!event_status_ && error == 0)
+ error = pthread_cond_wait(&event_cond_, &event_mutex_);
+ }
+
+ // NOTE(liulk): Exactly one thread will auto-reset this event. All
+ // the other threads will think it's unsignaled. This seems to be
+ // consistent with auto-reset events in WIN32.
+ if (error == 0 && !is_manual_reset_)
+ event_status_ = false;
+
+ pthread_mutex_unlock(&event_mutex_);
+
+ return (error == 0);
+}
+
+#endif
+
+} // namespace talk_base
diff --git a/talk/base/event.h b/talk/base/event.h
new file mode 100644
index 0000000..757164c
--- /dev/null
+++ b/talk/base/event.h
@@ -0,0 +1,71 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_EVENT_H__
+#define TALK_BASE_EVENT_H__
+
+#if defined(WIN32)
+#include "talk/base/win32.h" // NOLINT: consider this a system header.
+#elif defined(POSIX)
+#include <pthread.h>
+#else
+#error "Must define either WIN32 or POSIX."
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class Event {
+ public:
+ Event(bool manual_reset, bool initially_signaled);
+ ~Event();
+
+ bool Set();
+ bool Reset();
+ bool Wait(int cms);
+
+ private:
+ bool EnsureInitialized();
+
+ bool is_manual_reset_;
+
+#if defined(WIN32)
+ bool is_initially_signaled_;
+ HANDLE event_handle_;
+#elif defined(POSIX)
+ bool event_status_;
+ bool event_mutex_initialized_;
+ pthread_mutex_t event_mutex_;
+ bool event_cond_initialized_;
+ pthread_cond_t event_cond_;
+#endif
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_EVENT_H__
diff --git a/talk/base/fileutils.cc b/talk/base/fileutils.cc
new file mode 100644
index 0000000..acfd2f8
--- /dev/null
+++ b/talk/base/fileutils.cc
@@ -0,0 +1,284 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 <cassert>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/pathutils.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+#include "talk/base/unixfilesystem.h"
+#include "talk/base/win32filesystem.h"
+
+#ifndef WIN32
+#define MAX_PATH 260
+#endif
+
+namespace talk_base {
+
+//////////////////////////
+// Directory Iterator //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+ // Constructor
+DirectoryIterator::DirectoryIterator()
+#ifdef _WIN32
+ : handle_(INVALID_HANDLE_VALUE) {
+#else
+ : dir_(NULL), dirent_(NULL) {
+#endif
+}
+
+ // Destructor
+DirectoryIterator::~DirectoryIterator() {
+#ifdef WIN32
+ if (handle_ != INVALID_HANDLE_VALUE)
+ ::FindClose(handle_);
+#else
+ if (dir_)
+ closedir(dir_);
+#endif
+}
+
+ // Starts traversing a directory.
+ // dir is the directory to traverse
+ // returns true if the directory exists and is valid
+bool DirectoryIterator::Iterate(const Pathname &dir) {
+ directory_ = dir.pathname();
+#ifdef WIN32
+ if (handle_ != INVALID_HANDLE_VALUE)
+ ::FindClose(handle_);
+ std::string d = dir.pathname() + '*';
+ handle_ = ::FindFirstFile(ToUtf16(d).c_str(), &data_);
+ if (handle_ == INVALID_HANDLE_VALUE)
+ return false;
+#else
+ if (dir_ != NULL)
+ closedir(dir_);
+ dir_ = ::opendir(directory_.c_str());
+ if (dir_ == NULL)
+ return false;
+ dirent_ = readdir(dir_);
+ if (dirent_ == NULL)
+ return false;
+
+ if (::stat(std::string(directory_ + Name()).c_str(), &stat_) != 0)
+ return false;
+#endif
+ return true;
+}
+
+ // Advances to the next file
+ // returns true if there were more files in the directory.
+bool DirectoryIterator::Next() {
+#ifdef WIN32
+ return ::FindNextFile(handle_, &data_) == TRUE;
+#else
+ dirent_ = ::readdir(dir_);
+ if (dirent_ == NULL)
+ return false;
+
+ return ::stat(std::string(directory_ + Name()).c_str(), &stat_) == 0;
+#endif
+}
+
+ // returns true if the file currently pointed to is a directory
+bool DirectoryIterator::IsDirectory() const {
+#ifdef WIN32
+ return (data_.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FALSE;
+#else
+ return S_ISDIR(stat_.st_mode);
+#endif
+}
+
+ // returns the name of the file currently pointed to
+std::string DirectoryIterator::Name() const {
+#ifdef WIN32
+ return ToUtf8(data_.cFileName);
+#else
+ assert(dirent_ != NULL);
+ return dirent_->d_name;
+#endif
+}
+
+ // returns the size of the file currently pointed to
+size_t DirectoryIterator::FileSize() const {
+#ifndef WIN32
+ return stat_.st_size;
+#else
+ return data_.nFileSizeLow;
+#endif
+}
+
+ // returns the last modified time of this file
+time_t DirectoryIterator::FileModifyTime() const {
+#ifdef WIN32
+ time_t val;
+ FileTimeToUnixTime(data_.ftLastWriteTime, &val);
+ return val;
+#else
+ return stat_.st_mtime;
+#endif
+}
+
+scoped_ptr<FilesystemInterface> Filesystem::default_filesystem_;
+FilesystemInterface *Filesystem::EnsureDefaultFilesystem() {
+ if (!default_filesystem_.get())
+#ifdef WIN32
+ default_filesystem_.reset(new Win32Filesystem());
+#else
+ default_filesystem_.reset(new UnixFilesystem());
+#endif
+ return default_filesystem_.get();
+}
+
+bool FilesystemInterface::CopyFolder(const Pathname &old_path,
+ const Pathname &new_path) {
+ VERIFY(IsFolder(old_path));
+ Pathname new_dir;
+ new_dir.SetFolder(new_path.pathname());
+ Pathname old_dir;
+ old_dir.SetFolder(old_path.pathname());
+ if (!CreateFolder(new_dir))
+ return false;
+ DirectoryIterator di;
+ di.Iterate(old_dir.pathname());
+ while (di.Next()) {
+ if (di.Name() == "." || di.Name() == "..")
+ continue;
+ Pathname source;
+ Pathname dest;
+ source.SetFolder(old_dir.pathname());
+ dest.SetFolder(new_path.pathname());
+ source.SetFilename(di.Name());
+ dest.SetFilename(di.Name());
+ if (!CopyFileOrFolder(source, dest))
+ return false;
+ }
+ return true;
+}
+
+bool FilesystemInterface::DeleteFolderContents(const Pathname &folder) {
+ bool success = true;
+ VERIFY(IsFolder(folder));
+ DirectoryIterator *di = IterateDirectory();
+ di->Iterate(folder);
+ while (di->Next()) {
+ if (di->Name() == "." || di->Name() == "..")
+ continue;
+ Pathname subdir;
+ subdir.SetFolder(folder.pathname());
+ if (di->IsDirectory()) {
+ subdir.AppendFolder(di->Name());
+ if (!DeleteFolderAndContents(subdir)) {
+ success = false;
+ }
+ } else {
+ subdir.SetFilename(di->Name());
+ if (!DeleteFile(subdir)) {
+ success = false;
+ }
+ }
+ }
+ delete di;
+ return success;
+}
+
+bool FilesystemInterface::CleanAppTempFolder() {
+ Pathname path;
+ if (!GetAppTempFolder(&path))
+ return false;
+ if (IsAbsent(path))
+ return true;
+ if (!IsTemporaryPath(path)) {
+ ASSERT(false);
+ return false;
+ }
+ return DeleteFolderContents(path);
+}
+
+Pathname Filesystem::GetCurrentDirectory() {
+ return EnsureDefaultFilesystem()->GetCurrentDirectory();
+}
+
+bool CreateUniqueFile(Pathname& path, bool create_empty) {
+ LOG(LS_INFO) << "Path " << path.pathname() << std::endl;
+ // If no folder is supplied, use the temporary folder
+ if (path.folder().empty()) {
+ Pathname temporary_path;
+ if (!Filesystem::GetTemporaryFolder(temporary_path, true, NULL)) {
+ printf("Get temp failed\n");
+ return false;
+ }
+ path.SetFolder(temporary_path.pathname());
+ }
+
+ // If no filename is supplied, use a temporary name
+ if (path.filename().empty()) {
+ std::string folder(path.folder());
+ std::string filename = Filesystem::TempFilename(folder, "gt");
+ path.SetPathname(filename);
+ if (!create_empty) {
+ Filesystem::DeleteFile(path.pathname());
+ }
+ return true;
+ }
+
+ // Otherwise, create a unique name based on the given filename
+ // foo.txt -> foo-N.txt
+ const std::string basename = path.basename();
+ const size_t MAX_VERSION = 100;
+ size_t version = 0;
+ while (version < MAX_VERSION) {
+ std::string pathname = path.pathname();
+
+ if (!Filesystem::IsFile(pathname)) {
+ if (create_empty) {
+ FileStream* fs = Filesystem::OpenFile(pathname, "w");
+ delete fs;
+ }
+ return true;
+ }
+ version += 1;
+ char version_base[MAX_PATH];
+ sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u",
+ basename.c_str(), version);
+ path.SetBasename(version_base);
+ }
+ return true;
+}
+
+} // namespace talk_base
diff --git a/talk/base/fileutils.h b/talk/base/fileutils.h
new file mode 100644
index 0000000..217cd83
--- /dev/null
+++ b/talk/base/fileutils.h
@@ -0,0 +1,456 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_H_
+#define TALK_BASE_FILEUTILS_H_
+
+#include <string>
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class FileStream;
+class Pathname;
+
+//////////////////////////
+// Directory Iterator //
+//////////////////////////
+
+// A DirectoryIterator is created with a given directory. It originally points
+// to the first file in the directory, and can be advanecd with Next(). This
+// allows you to get information about each file.
+
+class DirectoryIterator {
+ friend class Filesystem;
+ public:
+ // Constructor
+ DirectoryIterator();
+ // Destructor
+ virtual ~DirectoryIterator();
+
+ // Starts traversing a directory
+ // dir is the directory to traverse
+ // returns true if the directory exists and is valid
+ // The iterator will point to the first entry in the directory
+ virtual bool Iterate(const Pathname &path);
+
+ // Advances to the next file
+ // returns true if there were more files in the directory.
+ virtual bool Next();
+
+ // returns true if the file currently pointed to is a directory
+ virtual bool IsDirectory() const;
+
+ // returns the name of the file currently pointed to
+ virtual std::string Name() const;
+
+ // returns the size of the file currently pointed to
+ virtual size_t FileSize() const;
+
+ // returns the last modified time of the file currently pointed to
+ virtual time_t FileModifyTime() const;
+
+ // checks whether current file is a special directory file "." or ".."
+ bool IsDots() const {
+ std::string filename(Name());
+ return (filename.compare(".") == 0) || (filename.compare("..") == 0);
+ }
+
+ private:
+ std::string directory_;
+#ifdef WIN32
+ WIN32_FIND_DATA data_;
+ HANDLE handle_;
+#else
+ DIR *dir_;
+ struct dirent *dirent_;
+ struct stat stat_;
+#endif
+};
+
+enum FileTimeType { FTT_CREATED, FTT_MODIFIED, FTT_ACCESSED };
+
+class FilesystemInterface {
+ public:
+ virtual ~FilesystemInterface() {}
+
+ // Returns a DirectoryIterator for a given pathname.
+ // TODO: Do fancy abstracted stuff
+ virtual DirectoryIterator *IterateDirectory() {
+ return new DirectoryIterator();
+ }
+
+ // Opens a file. Returns an open StreamInterface if function succeeds.
+ // Otherwise, returns NULL.
+ virtual FileStream *OpenFile(const Pathname &filename,
+ const std::string &mode) = 0;
+
+ // Atomically creates an empty file accessible only to the current user if one
+ // does not already exist at the given path, otherwise fails. This is the only
+ // secure way to create a file in a shared temp directory (e.g., C:\Temp on
+ // Windows or /tmp on Linux).
+ // Note that if it is essential that a file be successfully created then the
+ // app must generate random names and retry on failure, or else it will be
+ // vulnerable to a trivial DoS.
+ virtual bool CreatePrivateFile(const Pathname &filename) = 0;
+
+ // This will attempt to delete the path located at filename.
+ // It ASSERTS and returns false if the path points to a folder or a
+ // non-existent file.
+ virtual bool DeleteFile(const Pathname &filename) = 0;
+
+ // This will attempt to delete the empty folder located at 'folder'
+ // It ASSERTS and returns false if the path points to a file or a non-existent
+ // folder. It fails normally if the folder is not empty or can otherwise
+ // not be deleted.
+ virtual bool DeleteEmptyFolder(const Pathname &folder) = 0;
+
+ // This will call IterateDirectory, to get a directory iterator, and then
+ // call DeleteFolderAndContents and DeleteFile on every path contained in this
+ // folder. If the folder is empty, this returns true.
+ virtual bool DeleteFolderContents(const Pathname &folder);
+
+ // This deletes the contents of a folder, recursively, and then deletes
+ // the folder itself.
+ virtual bool DeleteFolderAndContents(const Pathname &folder) {
+ return DeleteFolderContents(folder) && DeleteEmptyFolder(folder);
+ }
+
+ // This will delete whatever is located at path, be it a file or a folder.
+ // If it is a folder, it will delete it recursively by calling
+ // DeleteFolderAndContents
+ bool DeleteFileOrFolder(const Pathname &path) {
+ if (IsFolder(path))
+ return DeleteFolderAndContents(path);
+ else
+ return DeleteFile(path);
+ }
+
+ // Creates a directory. This will call itself recursively to create /foo/bar
+ // even if /foo does not exist. Returns true if the function succeeds.
+ virtual bool CreateFolder(const Pathname &pathname) = 0;
+
+ // This moves a file from old_path to new_path, where "old_path" is a
+ // plain file. This ASSERTs and returns false if old_path points to a
+ // directory, and returns true if the function succeeds.
+ // If the new path is on a different volume than the old path, this function
+ // will attempt to copy and, if that succeeds, delete the old path.
+ virtual bool MoveFolder(const Pathname &old_path,
+ const Pathname &new_path) = 0;
+
+ // This moves a directory from old_path to new_path, where "old_path" is a
+ // directory. This ASSERTs and returns false if old_path points to a plain
+ // file, and returns true if the function succeeds.
+ // If the new path is on a different volume, this function will attempt to
+ // copy and if that succeeds, delete the old path.
+ virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+ // This attempts to move whatever is located at old_path to new_path,
+ // be it a file or folder.
+ bool MoveFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+ if (IsFile(old_path)) {
+ return MoveFile(old_path, new_path);
+ } else {
+ return MoveFolder(old_path, new_path);
+ }
+ }
+
+ // This copies a file from old_path to new_path. This method ASSERTs and
+ // returns false if old_path is a folder, and returns true if the copy
+ // succeeds.
+ virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path) = 0;
+
+ // This copies a folder from old_path to new_path.
+ bool CopyFolder(const Pathname &old_path, const Pathname &new_path);
+
+ bool CopyFileOrFolder(const Pathname &old_path, const Pathname &new_path) {
+ if (IsFile(old_path))
+ return CopyFile(old_path, new_path);
+ else
+ return CopyFolder(old_path, new_path);
+ }
+
+ // Returns true if pathname refers to a directory
+ virtual bool IsFolder(const Pathname& pathname) = 0;
+
+ // Returns true if pathname refers to a file
+ virtual bool IsFile(const Pathname& pathname) = 0;
+
+ // Returns true if pathname refers to no filesystem object, every parent
+ // directory either exists, or is also absent.
+ virtual bool IsAbsent(const Pathname& pathname) = 0;
+
+ // Returns true if pathname represents a temporary location on the system.
+ virtual bool IsTemporaryPath(const Pathname& pathname) = 0;
+
+ // A folder appropriate for storing temporary files (Contents are
+ // automatically deleted when the program exits)
+ virtual bool GetTemporaryFolder(Pathname &path, bool create,
+ const std::string *append) = 0;
+
+ virtual std::string TempFilename(const Pathname &dir,
+ const std::string &prefix) = 0;
+
+ // Determines the size of the file indicated by path.
+ virtual bool GetFileSize(const Pathname& path, size_t* size) = 0;
+
+ // Determines a timestamp associated with the file indicated by path.
+ virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time) = 0;
+
+ // Returns the path to the running application.
+ // Note: This is not guaranteed to work on all platforms. Be aware of the
+ // limitations before using it, and robustly handle failure.
+ virtual bool GetAppPathname(Pathname* path) = 0;
+
+ // Get a folder that is unique to the current application, which is suitable
+ // for sharing data between executions of the app. If the per_user arg is
+ // true, the folder is also specific to the current user.
+ virtual bool GetAppDataFolder(Pathname* path, bool per_user) = 0;
+
+ // Get a temporary folder that is unique to the current user and application.
+ // TODO: Re-evaluate the goals of this function. We probably just need any
+ // directory that won't collide with another existing directory, and which
+ // will be cleaned up when the program exits.
+ virtual bool GetAppTempFolder(Pathname* path) = 0;
+
+ // Delete the contents of the folder returned by GetAppTempFolder
+ bool CleanAppTempFolder();
+
+ virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) = 0;
+
+ // Returns the absolute path of the current directory.
+ virtual Pathname GetCurrentDirectory() = 0;
+
+ // Note: These might go into some shared config section later, but they're
+ // used by some methods in this interface, so we're leaving them here for now.
+ void SetOrganizationName(const std::string& organization) {
+ organization_name_ = organization;
+ }
+ void GetOrganizationName(std::string* organization) {
+ ASSERT(NULL != organization);
+ *organization = organization_name_;
+ }
+ void SetApplicationName(const std::string& application) {
+ application_name_ = application;
+ }
+ void GetApplicationName(std::string* application) {
+ ASSERT(NULL != application);
+ *application = application_name_;
+ }
+
+ protected:
+ std::string organization_name_;
+ std::string application_name_;
+};
+
+class Filesystem {
+ public:
+ static FilesystemInterface *default_filesystem() {
+ ASSERT(default_filesystem_.get() != NULL);
+ return default_filesystem_.get();
+ }
+
+ static void set_default_filesystem(FilesystemInterface *filesystem) {
+ default_filesystem_.reset(filesystem);
+ }
+
+ static FilesystemInterface *swap_default_filesystem(
+ FilesystemInterface *filesystem) {
+ FilesystemInterface *cur = default_filesystem_.release();
+ default_filesystem_.reset(filesystem);
+ return cur;
+ }
+
+ static DirectoryIterator *IterateDirectory() {
+ return EnsureDefaultFilesystem()->IterateDirectory();
+ }
+
+ static bool CreateFolder(const Pathname &pathname) {
+ return EnsureDefaultFilesystem()->CreateFolder(pathname);
+ }
+
+ static FileStream *OpenFile(const Pathname &filename,
+ const std::string &mode) {
+ return EnsureDefaultFilesystem()->OpenFile(filename, mode);
+ }
+
+ static bool CreatePrivateFile(const Pathname &filename) {
+ return EnsureDefaultFilesystem()->CreatePrivateFile(filename);
+ }
+
+ static bool DeleteFile(const Pathname &filename) {
+ return EnsureDefaultFilesystem()->DeleteFile(filename);
+ }
+
+ static bool DeleteEmptyFolder(const Pathname &folder) {
+ return EnsureDefaultFilesystem()->DeleteEmptyFolder(folder);
+ }
+
+ static bool DeleteFolderContents(const Pathname &folder) {
+ return EnsureDefaultFilesystem()->DeleteFolderContents(folder);
+ }
+
+ static bool DeleteFolderAndContents(const Pathname &folder) {
+ return EnsureDefaultFilesystem()->DeleteFolderAndContents(folder);
+ }
+
+ static bool MoveFolder(const Pathname &old_path, const Pathname &new_path) {
+ return EnsureDefaultFilesystem()->MoveFolder(old_path, new_path);
+ }
+
+ static bool MoveFile(const Pathname &old_path, const Pathname &new_path) {
+ return EnsureDefaultFilesystem()->MoveFile(old_path, new_path);
+ }
+
+ static bool CopyFolder(const Pathname &old_path, const Pathname &new_path) {
+ return EnsureDefaultFilesystem()->CopyFolder(old_path, new_path);
+ }
+
+ static bool CopyFile(const Pathname &old_path, const Pathname &new_path) {
+ return EnsureDefaultFilesystem()->CopyFile(old_path, new_path);
+ }
+
+ static bool IsFolder(const Pathname& pathname) {
+ return EnsureDefaultFilesystem()->IsFolder(pathname);
+ }
+
+ static bool IsFile(const Pathname &pathname) {
+ return EnsureDefaultFilesystem()->IsFile(pathname);
+ }
+
+ static bool IsAbsent(const Pathname &pathname) {
+ return EnsureDefaultFilesystem()->IsAbsent(pathname);
+ }
+
+ static bool IsTemporaryPath(const Pathname& pathname) {
+ return EnsureDefaultFilesystem()->IsTemporaryPath(pathname);
+ }
+
+ static bool GetTemporaryFolder(Pathname &path, bool create,
+ const std::string *append) {
+ return EnsureDefaultFilesystem()->GetTemporaryFolder(path, create, append);
+ }
+
+ static std::string TempFilename(const Pathname &dir,
+ const std::string &prefix) {
+ return EnsureDefaultFilesystem()->TempFilename(dir, prefix);
+ }
+
+ static bool GetFileSize(const Pathname& path, size_t* size) {
+ return EnsureDefaultFilesystem()->GetFileSize(path, size);
+ }
+
+ static bool GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time) {
+ return EnsureDefaultFilesystem()->GetFileTime(path, which, time);
+ }
+
+ static bool GetAppPathname(Pathname* path) {
+ return EnsureDefaultFilesystem()->GetAppPathname(path);
+ }
+
+ static bool GetAppDataFolder(Pathname* path, bool per_user) {
+ return EnsureDefaultFilesystem()->GetAppDataFolder(path, per_user);
+ }
+
+ static bool GetAppTempFolder(Pathname* path) {
+ return EnsureDefaultFilesystem()->GetAppTempFolder(path);
+ }
+
+ static bool CleanAppTempFolder() {
+ return EnsureDefaultFilesystem()->CleanAppTempFolder();
+ }
+
+ static bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+ return EnsureDefaultFilesystem()->GetDiskFreeSpace(path, freebytes);
+ }
+
+ // Definition has to be in the .cc file due to returning forward-declared
+ // Pathname by value.
+ static Pathname GetCurrentDirectory();
+
+ static void SetOrganizationName(const std::string& organization) {
+ EnsureDefaultFilesystem()->SetOrganizationName(organization);
+ }
+
+ static void GetOrganizationName(std::string* organization) {
+ EnsureDefaultFilesystem()->GetOrganizationName(organization);
+ }
+
+ static void SetApplicationName(const std::string& application) {
+ EnsureDefaultFilesystem()->SetApplicationName(application);
+ }
+
+ static void GetApplicationName(std::string* application) {
+ EnsureDefaultFilesystem()->GetApplicationName(application);
+ }
+
+ private:
+ static scoped_ptr<FilesystemInterface> default_filesystem_;
+
+ static FilesystemInterface *EnsureDefaultFilesystem();
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Filesystem);
+};
+
+class FilesystemScope{
+ public:
+ explicit FilesystemScope(FilesystemInterface *new_fs) {
+ old_fs_ = Filesystem::swap_default_filesystem(new_fs);
+ }
+ ~FilesystemScope() {
+ Filesystem::set_default_filesystem(old_fs_);
+ }
+ private:
+ FilesystemInterface* old_fs_;
+ DISALLOW_IMPLICIT_CONSTRUCTORS(FilesystemScope);
+};
+
+// Generates a unique filename based on the input path. If no path component
+// is specified, it uses the temporary directory. If a filename is provided,
+// up to 100 variations of form basename-N.extension are tried. When
+// create_empty is true, an empty file of this name is created (which
+// decreases the chance of a temporary filename collision with another
+// process).
+bool CreateUniqueFile(Pathname& path, bool create_empty);
+
+} // namespace talk_base
+
+#endif // TALK_BASE_FILEUTILS_H_
+
diff --git a/talk/base/firewallsocketserver.cc b/talk/base/firewallsocketserver.cc
new file mode 100644
index 0000000..a99c72e
--- /dev/null
+++ b/talk/base/firewallsocketserver.cc
@@ -0,0 +1,246 @@
+/*
+ * 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 "talk/base/firewallsocketserver.h"
+
+#include <cassert>
+#include <algorithm>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+class FirewallSocket : public AsyncSocketAdapter {
+ public:
+ FirewallSocket(FirewallSocketServer* server, AsyncSocket* socket, int type)
+ : AsyncSocketAdapter(socket), server_(server), type_(type) {
+ }
+
+ virtual int Connect(const SocketAddress& addr) {
+ if (type_ == SOCK_STREAM) {
+ if (!server_->Check(FP_TCP, GetLocalAddress(), addr)) {
+ LOG(LS_VERBOSE) << "FirewallSocket outbound TCP connection from "
+ << GetLocalAddress().ToString() << " to "
+ << addr.ToString() << " denied";
+ // TODO: Handle this asynchronously.
+ SetError(EHOSTUNREACH);
+ return SOCKET_ERROR;
+ }
+ }
+ return AsyncSocketAdapter::Connect(addr);
+ }
+ virtual int Send(const void* pv, size_t cb) {
+ return SendTo(pv, cb, GetRemoteAddress());
+ }
+ virtual int SendTo(const void* pv, size_t cb, const SocketAddress& addr) {
+ if (type_ == SOCK_DGRAM) {
+ if (!server_->Check(FP_UDP, GetLocalAddress(), addr)) {
+ LOG(LS_VERBOSE) << "FirewallSocket outbound UDP packet from "
+ << GetLocalAddress().ToString() << " to "
+ << addr.ToString() << " dropped";
+ return static_cast<int>(cb);
+ }
+ }
+ return AsyncSocketAdapter::SendTo(pv, cb, addr);
+ }
+ virtual int Recv(void* pv, size_t cb) {
+ SocketAddress addr;
+ return RecvFrom(pv, cb, &addr);
+ }
+ virtual int RecvFrom(void* pv, size_t cb, SocketAddress* paddr) {
+ if (type_ == SOCK_DGRAM) {
+ while (true) {
+ int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+ if (res <= 0)
+ return res;
+ if (server_->Check(FP_UDP, *paddr, GetLocalAddress()))
+ return res;
+ LOG(LS_VERBOSE) << "FirewallSocket inbound UDP packet from "
+ << paddr->ToString() << " to "
+ << GetLocalAddress().ToString() << " dropped";
+ }
+ }
+ return AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+ }
+
+ virtual int Listen(int backlog) {
+ if (!server_->tcp_listen_enabled()) {
+ LOG(LS_VERBOSE) << "FirewallSocket listen attempt denied";
+ return -1;
+ }
+
+ return AsyncSocketAdapter::Listen(backlog);
+ }
+ virtual AsyncSocket* Accept(SocketAddress* paddr) {
+ SocketAddress addr;
+ while (AsyncSocket* sock = AsyncSocketAdapter::Accept(&addr)) {
+ if (server_->Check(FP_TCP, addr, GetLocalAddress())) {
+ if (paddr)
+ *paddr = addr;
+ return sock;
+ }
+ sock->Close();
+ delete sock;
+ LOG(LS_VERBOSE) << "FirewallSocket inbound TCP connection from "
+ << addr.ToString() << " to "
+ << GetLocalAddress().ToString() << " denied";
+ }
+ return 0;
+ }
+
+ private:
+ FirewallSocketServer* server_;
+ int type_;
+};
+
+FirewallSocketServer::FirewallSocketServer(SocketServer* server,
+ FirewallManager* manager,
+ bool should_delete_server)
+ : server_(server), manager_(manager),
+ should_delete_server_(should_delete_server),
+ udp_sockets_enabled_(true), tcp_sockets_enabled_(true),
+ tcp_listen_enabled_(true) {
+ if (manager_)
+ manager_->AddServer(this);
+}
+
+FirewallSocketServer::~FirewallSocketServer() {
+ if (manager_)
+ manager_->RemoveServer(this);
+
+ if (server_ && should_delete_server_) {
+ delete server_;
+ server_ = NULL;
+ }
+}
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+ FirewallDirection d,
+ const SocketAddress& addr) {
+ SocketAddress src, dst;
+ if (d == FD_IN) {
+ dst = addr;
+ } else {
+ src = addr;
+ }
+ AddRule(allow, p, src, dst);
+}
+
+
+void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p,
+ const SocketAddress& src,
+ const SocketAddress& dst) {
+ Rule r;
+ r.allow = allow;
+ r.p = p;
+ r.src = src;
+ r.dst = dst;
+ CritScope scope(&crit_);
+ rules_.push_back(r);
+}
+
+void FirewallSocketServer::ClearRules() {
+ CritScope scope(&crit_);
+ rules_.clear();
+}
+
+bool FirewallSocketServer::Check(FirewallProtocol p,
+ const SocketAddress& src,
+ const SocketAddress& dst) {
+ CritScope scope(&crit_);
+ for (size_t i = 0; i < rules_.size(); ++i) {
+ const Rule& r = rules_[i];
+ if ((r.p != p) && (r.p != FP_ANY))
+ continue;
+ if ((r.src.ip() != src.ip()) && !r.src.IsAny())
+ continue;
+ if ((r.src.port() != src.port()) && (r.src.port() != 0))
+ continue;
+ if ((r.dst.ip() != dst.ip()) && !r.dst.IsAny())
+ continue;
+ if ((r.dst.port() != dst.port()) && (r.dst.port() != 0))
+ continue;
+ return r.allow;
+ }
+ return true;
+}
+
+Socket* FirewallSocketServer::CreateSocket(int type) {
+ return WrapSocket(server_->CreateAsyncSocket(type), type);
+}
+
+AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) {
+ return WrapSocket(server_->CreateAsyncSocket(type), type);
+}
+
+AsyncSocket* FirewallSocketServer::WrapSocket(AsyncSocket* sock, int type) {
+ if (!sock ||
+ (type == SOCK_STREAM && !tcp_sockets_enabled_) ||
+ (type == SOCK_DGRAM && !udp_sockets_enabled_)) {
+ LOG(LS_VERBOSE) << "FirewallSocketServer socket creation denied";
+ return NULL;
+ }
+ return new FirewallSocket(this, sock, type);
+}
+
+FirewallManager::FirewallManager() {
+}
+
+FirewallManager::~FirewallManager() {
+ assert(servers_.empty());
+}
+
+void FirewallManager::AddServer(FirewallSocketServer* server) {
+ CritScope scope(&crit_);
+ servers_.push_back(server);
+}
+
+void FirewallManager::RemoveServer(FirewallSocketServer* server) {
+ CritScope scope(&crit_);
+ servers_.erase(std::remove(servers_.begin(), servers_.end(), server),
+ servers_.end());
+}
+
+void FirewallManager::AddRule(bool allow, FirewallProtocol p,
+ FirewallDirection d, const SocketAddress& addr) {
+ CritScope scope(&crit_);
+ for (std::vector<FirewallSocketServer*>::const_iterator it =
+ servers_.begin(); it != servers_.end(); ++it) {
+ (*it)->AddRule(allow, p, d, addr);
+ }
+}
+
+void FirewallManager::ClearRules() {
+ CritScope scope(&crit_);
+ for (std::vector<FirewallSocketServer*>::const_iterator it =
+ servers_.begin(); it != servers_.end(); ++it) {
+ (*it)->ClearRules();
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/firewallsocketserver.h b/talk/base/firewallsocketserver.h
new file mode 100644
index 0000000..94ba2d2
--- /dev/null
+++ b/talk/base/firewallsocketserver.h
@@ -0,0 +1,133 @@
+/*
+ * 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_FIREWALLSOCKETSERVER_H_
+#define TALK_BASE_FIREWALLSOCKETSERVER_H_
+
+#include <vector>
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class FirewallManager;
+
+// This SocketServer shim simulates a rule-based firewall server.
+
+enum FirewallProtocol { FP_UDP, FP_TCP, FP_ANY };
+enum FirewallDirection { FD_IN, FD_OUT, FD_ANY };
+
+class FirewallSocketServer : public SocketServer {
+ public:
+ FirewallSocketServer(SocketServer * server,
+ FirewallManager * manager = NULL,
+ bool should_delete_server = false);
+ virtual ~FirewallSocketServer();
+
+ SocketServer* socketserver() const { return server_; }
+ void set_socketserver(SocketServer* server) {
+ if (server_ && should_delete_server_) {
+ delete server_;
+ server_ = NULL;
+ should_delete_server_ = false;
+ }
+ server_ = server;
+ }
+
+ // Settings to control whether CreateSocket or Socket::Listen succeed.
+ void set_udp_sockets_enabled(bool enabled) { udp_sockets_enabled_ = enabled; }
+ void set_tcp_sockets_enabled(bool enabled) { tcp_sockets_enabled_ = enabled; }
+ bool tcp_listen_enabled() const { return tcp_listen_enabled_; }
+ void set_tcp_listen_enabled(bool enabled) { tcp_listen_enabled_ = enabled; }
+
+ // Rules govern the behavior of Connect/Accept/Send/Recv attempts.
+ void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+ FirewallDirection d = FD_ANY,
+ const SocketAddress& addr = SocketAddress());
+ void AddRule(bool allow, FirewallProtocol p,
+ const SocketAddress& src, const SocketAddress& dst);
+ void ClearRules();
+
+ bool Check(FirewallProtocol p,
+ const SocketAddress& src, const SocketAddress& dst);
+
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+ virtual void SetMessageQueue(MessageQueue* queue) {
+ server_->SetMessageQueue(queue);
+ }
+ virtual bool Wait(int cms, bool process_io) {
+ return server_->Wait(cms, process_io);
+ }
+ virtual void WakeUp() {
+ return server_->WakeUp();
+ }
+
+ Socket * WrapSocket(Socket * sock, int type);
+ AsyncSocket * WrapSocket(AsyncSocket * sock, int type);
+
+ private:
+ SocketServer * server_;
+ FirewallManager * manager_;
+ CriticalSection crit_;
+ struct Rule {
+ bool allow;
+ FirewallProtocol p;
+ FirewallDirection d;
+ SocketAddress src;
+ SocketAddress dst;
+ };
+ std::vector<Rule> rules_;
+ bool should_delete_server_;
+ bool udp_sockets_enabled_;
+ bool tcp_sockets_enabled_;
+ bool tcp_listen_enabled_;
+};
+
+// FirewallManager allows you to manage firewalls in multiple threads together
+
+class FirewallManager {
+ public:
+ FirewallManager();
+ ~FirewallManager();
+
+ void AddServer(FirewallSocketServer * server);
+ void RemoveServer(FirewallSocketServer * server);
+
+ void AddRule(bool allow, FirewallProtocol p = FP_ANY,
+ FirewallDirection d = FD_ANY,
+ const SocketAddress& addr = SocketAddress());
+ void ClearRules();
+
+ private:
+ CriticalSection crit_;
+ std::vector<FirewallSocketServer *> servers_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_FIREWALLSOCKETSERVER_H_
diff --git a/talk/base/flags.cc b/talk/base/flags.cc
new file mode 100644
index 0000000..09a8edf
--- /dev/null
+++ b/talk/base/flags.cc
@@ -0,0 +1,324 @@
+/*
+ * libjingle
+ * Copyright 2006, 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#endif
+
+#include "talk/base/flags.h"
+
+
+// -----------------------------------------------------------------------------
+// Implementation of Flag
+
+Flag::Flag(const char* file, const char* name, const char* comment,
+ Type type, void* variable, FlagValue default__)
+ : file_(file),
+ name_(name),
+ comment_(comment),
+ type_(type),
+ variable_(reinterpret_cast<FlagValue*>(variable)),
+ default_(default__) {
+ FlagList::Register(this);
+}
+
+
+void Flag::SetToDefault() {
+ // Note that we cannot simply do '*variable_ = default_;' since
+ // flag variables are not really of type FlagValue and thus may
+ // be smaller! The FlagValue union is simply 'overlayed' on top
+ // of a flag variable for convenient access. Since union members
+ // are guarantee to be aligned at the beginning, this works.
+ switch (type_) {
+ case Flag::BOOL:
+ variable_->b = default_.b;
+ return;
+ case Flag::INT:
+ variable_->i = default_.i;
+ return;
+ case Flag::FLOAT:
+ variable_->f = default_.f;
+ return;
+ case Flag::STRING:
+ variable_->s = default_.s;
+ return;
+ }
+ UNREACHABLE();
+}
+
+
+static const char* Type2String(Flag::Type type) {
+ switch (type) {
+ case Flag::BOOL: return "bool";
+ case Flag::INT: return "int";
+ case Flag::FLOAT: return "float";
+ case Flag::STRING: return "string";
+ }
+ UNREACHABLE();
+ return NULL;
+}
+
+
+static void PrintFlagValue(Flag::Type type, FlagValue* p) {
+ switch (type) {
+ case Flag::BOOL:
+ printf("%s", (p->b ? "true" : "false"));
+ return;
+ case Flag::INT:
+ printf("%d", p->i);
+ return;
+ case Flag::FLOAT:
+ printf("%f", p->f);
+ return;
+ case Flag::STRING:
+ printf("%s", p->s);
+ return;
+ }
+ UNREACHABLE();
+}
+
+
+void Flag::Print(bool print_current_value) {
+ printf(" --%s (%s) type: %s default: ", name_, comment_,
+ Type2String(type_));
+ PrintFlagValue(type_, &default_);
+ if (print_current_value) {
+ printf(" current value: ");
+ PrintFlagValue(type_, variable_);
+ }
+ printf("\n");
+}
+
+
+// -----------------------------------------------------------------------------
+// Implementation of FlagList
+
+Flag* FlagList::list_ = NULL;
+
+
+FlagList::FlagList() {
+ list_ = NULL;
+}
+
+void FlagList::Print(const char* file, bool print_current_value) {
+ // Since flag registration is likely by file (= C++ file),
+ // we don't need to sort by file and still get grouped output.
+ const char* current = NULL;
+ for (Flag* f = list_; f != NULL; f = f->next()) {
+ if (file == NULL || file == f->file()) {
+ if (current != f->file()) {
+ printf("Flags from %s:\n", f->file());
+ current = f->file();
+ }
+ f->Print(print_current_value);
+ }
+ }
+}
+
+
+Flag* FlagList::Lookup(const char* name) {
+ Flag* f = list_;
+ while (f != NULL && strcmp(name, f->name()) != 0)
+ f = f->next();
+ return f;
+}
+
+
+void FlagList::SplitArgument(const char* arg,
+ char* buffer, int buffer_size,
+ const char** name, const char** value,
+ bool* is_bool) {
+ *name = NULL;
+ *value = NULL;
+ *is_bool = false;
+
+ if (*arg == '-') {
+ // find the begin of the flag name
+ arg++; // remove 1st '-'
+ if (*arg == '-')
+ arg++; // remove 2nd '-'
+ if (arg[0] == 'n' && arg[1] == 'o') {
+ arg += 2; // remove "no"
+ *is_bool = true;
+ }
+ *name = arg;
+
+ // find the end of the flag name
+ while (*arg != '\0' && *arg != '=')
+ arg++;
+
+ // get the value if any
+ if (*arg == '=') {
+ // make a copy so we can NUL-terminate flag name
+ int n = arg - *name;
+ if (n >= buffer_size)
+ Fatal(__FILE__, __LINE__, "CHECK(%s) failed", "n < buffer_size");
+ memcpy(buffer, *name, n * sizeof(char));
+ buffer[n] = '\0';
+ *name = buffer;
+ // get the value
+ *value = arg + 1;
+ }
+ }
+}
+
+
+int FlagList::SetFlagsFromCommandLine(int* argc, const char** argv,
+ bool remove_flags) {
+ // parse arguments
+ for (int i = 1; i < *argc; /* see below */) {
+ int j = i; // j > 0
+ const char* arg = argv[i++];
+
+ // split arg into flag components
+ char buffer[1024];
+ const char* name;
+ const char* value;
+ bool is_bool;
+ SplitArgument(arg, buffer, sizeof buffer, &name, &value, &is_bool);
+
+ if (name != NULL) {
+ // lookup the flag
+ Flag* flag = Lookup(name);
+ if (flag == NULL) {
+ fprintf(stderr, "Error: unrecognized flag %s\n", arg);
+ return j;
+ }
+
+ // if we still need a flag value, use the next argument if available
+ if (flag->type() != Flag::BOOL && value == NULL) {
+ if (i < *argc) {
+ value = argv[i++];
+ } else {
+ fprintf(stderr, "Error: missing value for flag %s of type %s\n",
+ arg, Type2String(flag->type()));
+ return j;
+ }
+ }
+
+ // set the flag
+ char empty[] = { '\0' };
+ char* endp = empty;
+ switch (flag->type()) {
+ case Flag::BOOL:
+ *flag->bool_variable() = !is_bool;
+ break;
+ case Flag::INT:
+ *flag->int_variable() = strtol(value, &endp, 10);
+ break;
+ case Flag::FLOAT:
+ *flag->float_variable() = strtod(value, &endp);
+ break;
+ case Flag::STRING:
+ *flag->string_variable() = value;
+ break;
+ }
+
+ // handle errors
+ if ((flag->type() == Flag::BOOL && value != NULL) ||
+ (flag->type() != Flag::BOOL && is_bool) ||
+ *endp != '\0') {
+ fprintf(stderr, "Error: illegal value for flag %s of type %s\n",
+ arg, Type2String(flag->type()));
+ return j;
+ }
+
+ // remove the flag & value from the command
+ if (remove_flags)
+ while (j < i)
+ argv[j++] = NULL;
+ }
+ }
+
+ // shrink the argument list
+ if (remove_flags) {
+ int j = 1;
+ for (int i = 1; i < *argc; i++) {
+ if (argv[i] != NULL)
+ argv[j++] = argv[i];
+ }
+ *argc = j;
+ }
+
+ // parsed all flags successfully
+ return 0;
+}
+
+void FlagList::Register(Flag* flag) {
+ assert(flag != NULL && strlen(flag->name()) > 0);
+ if (Lookup(flag->name()) != NULL)
+ Fatal(flag->file(), 0, "flag %s declared twice", flag->name());
+ flag->next_ = list_;
+ list_ = flag;
+}
+
+#ifdef WIN32
+WindowsCommandLineArguments::WindowsCommandLineArguments() {
+ // start by getting the command line.
+ LPTSTR command_line = ::GetCommandLine();
+ // now, convert it to a list of wide char strings.
+ LPWSTR *wide_argv = ::CommandLineToArgvW(command_line, &argc_);
+ // now allocate an array big enough to hold that many string pointers.
+ argv_ = new char*[argc_];
+
+ // iterate over the returned wide strings;
+ for(int i = 0; i < argc_; ++i) {
+ // for each, create a char buffer big enough to hold it; so, find out
+ // how much space we need.
+ int len8 = WideCharToMultiByte(CP_UTF8, 0, wide_argv[i],
+ wcslen(wide_argv[i]), NULL, 0,
+ NULL, NULL);
+ // then allocate the buffer...
+ char *buffer = new char[1 + len8]; // +1 for trailing \0
+ // and do the conversion.
+ WideCharToMultiByte(CP_UTF8, 0, wide_argv[i],
+ wcslen(wide_argv[i]), buffer, len8,
+ NULL, NULL);
+ // WideCharToMultibyte doesn't give us a trailing \0, so we add it.
+ buffer[len8] = '\0';
+ // make sure the argv array has the right string at this point.
+ argv_[i] = buffer;
+ }
+ LocalFree(wide_argv);
+}
+
+WindowsCommandLineArguments::~WindowsCommandLineArguments() {
+ // need to free each string in the array, and then the array.
+ for(int i = 0; i < argc_; i++) {
+ delete[] argv_[i];
+ }
+
+ delete[] argv_;
+}
+#endif // WIN32
+
diff --git a/talk/base/flags.h b/talk/base/flags.h
new file mode 100644
index 0000000..a6eee00
--- /dev/null
+++ b/talk/base/flags.h
@@ -0,0 +1,281 @@
+/*
+ * libjingle
+ * Copyright 2006, 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.
+ */
+
+// Flags are defined and declared using DEFINE_xxx and DECLARE_xxx macros,
+// where xxx is the flag type. Flags are referred to via FLAG_yyy,
+// where yyy is the flag name. For intialization and iteration of flags,
+// see the FlagList class. For full programmatic access to any
+// flag, see the Flag class.
+//
+// The implementation only relies and basic C++ functionality
+// and needs no special library or STL support.
+
+#ifndef TALK_BASE_FLAGS_H__
+#define TALK_BASE_FLAGS_H__
+
+#include <assert.h>
+
+#include "talk/base/checks.h"
+#include "talk/base/common.h"
+
+// Internal use only.
+union FlagValue {
+ // Note: Because in C++ non-bool values are silently converted into
+ // bool values ('bool b = "false";' results in b == true!), we pass
+ // and int argument to New_BOOL as this appears to be safer - sigh.
+ // In particular, it prevents the (not uncommon!) bug where a bool
+ // flag is defined via: DEFINE_bool(flag, "false", "some comment");.
+ static FlagValue New_BOOL(int b) {
+ FlagValue v;
+ v.b = (b != 0);
+ return v;
+ }
+
+ static FlagValue New_INT(int i) {
+ FlagValue v;
+ v.i = i;
+ return v;
+ }
+
+ static FlagValue New_FLOAT(float f) {
+ FlagValue v;
+ v.f = f;
+ return v;
+ }
+
+ static FlagValue New_STRING(const char* s) {
+ FlagValue v;
+ v.s = s;
+ return v;
+ }
+
+ bool b;
+ int i;
+ double f;
+ const char* s;
+};
+
+
+// Each flag can be accessed programmatically via a Flag object.
+class Flag {
+ public:
+ enum Type { BOOL, INT, FLOAT, STRING };
+
+ // Internal use only.
+ Flag(const char* file, const char* name, const char* comment,
+ Type type, void* variable, FlagValue default_);
+
+ // General flag information
+ const char* file() const { return file_; }
+ const char* name() const { return name_; }
+ const char* comment() const { return comment_; }
+
+ // Flag type
+ Type type() const { return type_; }
+
+ // Flag variables
+ bool* bool_variable() const {
+ assert(type_ == BOOL);
+ return &variable_->b;
+ }
+
+ int* int_variable() const {
+ assert(type_ == INT);
+ return &variable_->i;
+ }
+
+ double* float_variable() const {
+ assert(type_ == FLOAT);
+ return &variable_->f;
+ }
+
+ const char** string_variable() const {
+ assert(type_ == STRING);
+ return &variable_->s;
+ }
+
+ // Default values
+ bool bool_default() const {
+ assert(type_ == BOOL);
+ return default_.b;
+ }
+
+ int int_default() const {
+ assert(type_ == INT);
+ return default_.i;
+ }
+
+ double float_default() const {
+ assert(type_ == FLOAT);
+ return default_.f;
+ }
+
+ const char* string_default() const {
+ assert(type_ == STRING);
+ return default_.s;
+ }
+
+ // Resets a flag to its default value
+ void SetToDefault();
+
+ // Iteration support
+ Flag* next() const { return next_; }
+
+ // Prints flag information. The current flag value is only printed
+ // if print_current_value is set.
+ void Print(bool print_current_value);
+
+ private:
+ const char* file_;
+ const char* name_;
+ const char* comment_;
+
+ Type type_;
+ FlagValue* variable_;
+ FlagValue default_;
+
+ Flag* next_;
+
+ friend class FlagList; // accesses next_
+};
+
+
+// Internal use only.
+#define DEFINE_FLAG(type, c_type, name, default, comment) \
+ /* define and initialize the flag */ \
+ c_type FLAG_##name = (default); \
+ /* register the flag */ \
+ static Flag Flag_##name(__FILE__, #name, (comment), \
+ Flag::type, &FLAG_##name, \
+ FlagValue::New_##type(default))
+
+
+// Internal use only.
+#define DECLARE_FLAG(c_type, name) \
+ /* declare the external flag */ \
+ extern c_type FLAG_##name
+
+
+// Use the following macros to define a new flag:
+#define DEFINE_bool(name, default, comment) \
+ DEFINE_FLAG(BOOL, bool, name, default, comment)
+#define DEFINE_int(name, default, comment) \
+ DEFINE_FLAG(INT, int, name, default, comment)
+#define DEFINE_float(name, default, comment) \
+ DEFINE_FLAG(FLOAT, double, name, default, comment)
+#define DEFINE_string(name, default, comment) \
+ DEFINE_FLAG(STRING, const char*, name, default, comment)
+
+
+// Use the following macros to declare a flag defined elsewhere:
+#define DECLARE_bool(name) DECLARE_FLAG(bool, name)
+#define DECLARE_int(name) DECLARE_FLAG(int, name)
+#define DECLARE_float(name) DECLARE_FLAG(double, name)
+#define DECLARE_string(name) DECLARE_FLAG(const char*, name)
+
+
+// The global list of all flags.
+class FlagList {
+ public:
+ FlagList();
+
+ // The NULL-terminated list of all flags. Traverse with Flag::next().
+ static Flag* list() { return list_; }
+
+ // If file != NULL, prints information for all flags defined in file;
+ // otherwise prints information for all flags in all files. The current
+ // flag value is only printed if print_current_value is set.
+ static void Print(const char* file, bool print_current_value);
+
+ // Lookup a flag by name. Returns the matching flag or NULL.
+ static Flag* Lookup(const char* name);
+
+ // Helper function to parse flags: Takes an argument arg and splits it into
+ // a flag name and flag value (or NULL if they are missing). is_bool is set
+ // if the arg started with "-no" or "--no". The buffer may be used to NUL-
+ // terminate the name, it must be large enough to hold any possible name.
+ static void SplitArgument(const char* arg,
+ char* buffer, int buffer_size,
+ const char** name, const char** value,
+ bool* is_bool);
+
+ // Set the flag values by parsing the command line. If remove_flags
+ // is set, the flags and associated values are removed from (argc,
+ // argv). Returns 0 if no error occurred. Otherwise, returns the
+ // argv index > 0 for the argument where an error occurred. In that
+ // case, (argc, argv) will remain unchanged indepdendent of the
+ // remove_flags value, and no assumptions about flag settings should
+ // be made.
+ //
+ // The following syntax for flags is accepted (both '-' and '--' are ok):
+ //
+ // --flag (bool flags only)
+ // --noflag (bool flags only)
+ // --flag=value (non-bool flags only, no spaces around '=')
+ // --flag value (non-bool flags only)
+ static int SetFlagsFromCommandLine(int* argc,
+ const char** argv,
+ bool remove_flags);
+ static inline int SetFlagsFromCommandLine(int* argc,
+ char** argv,
+ bool remove_flags) {
+ return SetFlagsFromCommandLine(argc, const_cast<const char**>(argv),
+ remove_flags);
+ }
+
+ // Registers a new flag. Called during program initialization. Not
+ // thread-safe.
+ static void Register(Flag* flag);
+
+ private:
+ static Flag* list_;
+};
+
+#ifdef WIN32
+// A helper class to translate Windows command line arguments into UTF8,
+// which then allows us to just pass them to the flags system.
+// This encapsulates all the work of getting the command line and translating
+// it to an array of 8-bit strings; all you have to do is create one of these,
+// and then call argc() and argv().
+class WindowsCommandLineArguments {
+ public:
+ WindowsCommandLineArguments();
+ ~WindowsCommandLineArguments();
+
+ int argc() { return argc_; }
+ char **argv() { return argv_; }
+ private:
+ int argc_;
+ char **argv_;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(WindowsCommandLineArguments);
+};
+#endif // WIN32
+
+
+#endif // SHARED_COMMANDLINEFLAGS_FLAGS_H__
diff --git a/talk/base/helpers.cc b/talk/base/helpers.cc
new file mode 100644
index 0000000..f8d8a9f
--- /dev/null
+++ b/talk/base/helpers.cc
@@ -0,0 +1,257 @@
+/*
+ * 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 "talk/base/helpers.h"
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <ntsecapi.h>
+#else
+#ifdef SSL_USE_OPENSSL
+#include <openssl/rand.h>
+#endif
+#endif
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+// Base class for RNG implementations.
+class RandomGenerator {
+ public:
+ virtual ~RandomGenerator() {}
+ virtual bool Init(const void* seed, size_t len) = 0;
+ virtual bool Generate(void* buf, size_t len) = 0;
+};
+
+// The real random generators, using either CryptoAPI or OpenSSL.
+// We also support the 'old' generator on Mac/Linux until we have time to
+// fully test the OpenSSL one.
+#ifdef WIN32
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+ SecureRandomGenerator() : advapi32_(NULL), rtl_gen_random_(NULL) {}
+ ~SecureRandomGenerator() {
+ FreeLibrary(advapi32_);
+ }
+
+ virtual bool Init(const void* seed, size_t seed_len) {
+ // We don't do any additional seeding on Win32, we just use the CryptoAPI
+ // RNG (which is exposed as a hidden function off of ADVAPI32 so that we
+ // don't need to drag in all of CryptoAPI)
+ if (rtl_gen_random_) {
+ return true;
+ }
+
+ advapi32_ = LoadLibrary(L"advapi32.dll");
+ if (!advapi32_) {
+ return false;
+ }
+
+ rtl_gen_random_ = reinterpret_cast<RtlGenRandomProc>(
+ GetProcAddress(advapi32_, "SystemFunction036"));
+ if (!rtl_gen_random_) {
+ FreeLibrary(advapi32_);
+ return false;
+ }
+
+ return true;
+ }
+ virtual bool Generate(void* buf, size_t len) {
+ if (!rtl_gen_random_ && !Init(NULL, 0)) {
+ return false;
+ }
+ return (rtl_gen_random_(buf, len) != FALSE);
+ }
+
+ private:
+ typedef BOOL (WINAPI *RtlGenRandomProc)(PVOID, ULONG);
+ HINSTANCE advapi32_;
+ RtlGenRandomProc rtl_gen_random_;
+};
+#else
+#ifndef SSL_USE_OPENSSL
+// The old RNG.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+ SecureRandomGenerator() : seed_(1) {
+ }
+ ~SecureRandomGenerator() {
+ }
+ virtual bool Init(const void* seed, size_t len) {
+ uint32 hash = 0;
+ for (size_t i = 0; i < len; ++i) {
+ hash = ((hash << 2) + hash) + static_cast<const char*>(seed)[i];
+ }
+
+ seed_ = Time() ^ hash;
+ return true;
+ }
+ virtual bool Generate(void* buf, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ static_cast<uint8*>(buf)[i] = static_cast<uint8>(GetRandom());
+ }
+ return true;
+ }
+
+ private:
+ int GetRandom() {
+ return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff;
+ }
+ int seed_;
+};
+#else
+// The OpenSSL RNG. Need to make sure it doesn't run out of entropy.
+class SecureRandomGenerator : public RandomGenerator {
+ public:
+ SecureRandomGenerator() : inited_(false) {
+ }
+ ~SecureRandomGenerator() {
+ }
+ virtual bool Init(const void* seed, size_t len) {
+ // By default, seed from the system state.
+ if (!inited_) {
+ if (RAND_poll() <= 0) {
+ return false;
+ }
+ inited_ = true;
+ }
+ // Allow app data to be mixed in, if provided.
+ if (seed) {
+ RAND_seed(seed, len);
+ }
+ return true;
+ }
+ virtual bool Generate(void* buf, size_t len) {
+ if (!inited_ && !Init(NULL, 0)) {
+ return false;
+ }
+ return (RAND_bytes(reinterpret_cast<unsigned char*>(buf), len) > 0);
+ }
+
+ private:
+ bool inited_;
+};
+#endif // SSL_USE_OPENSSL
+#endif // WIN32
+
+// A test random generator, for predictable output.
+class TestRandomGenerator : public RandomGenerator {
+ public:
+ TestRandomGenerator() : seed_(7) {
+ }
+ ~TestRandomGenerator() {
+ }
+ virtual bool Init(const void* seed, size_t len) {
+ return true;
+ }
+ virtual bool Generate(void* buf, size_t len) {
+ for (size_t i = 0; i < len; ++i) {
+ static_cast<uint8*>(buf)[i] = static_cast<uint8>(GetRandom());
+ }
+ return true;
+ }
+
+ private:
+ int GetRandom() {
+ return ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff;
+ }
+ int seed_;
+};
+
+// TODO: Use Base64::Base64Table instead.
+static const char BASE64[64] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
+ 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
+ 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
+};
+
+static scoped_ptr<RandomGenerator> g_rng(new SecureRandomGenerator());
+
+void SetRandomTestMode(bool test) {
+ if (!test) {
+ g_rng.reset(new SecureRandomGenerator());
+ } else {
+ g_rng.reset(new TestRandomGenerator());
+ }
+}
+
+bool InitRandom(int seed) {
+ return InitRandom(reinterpret_cast<const char*>(&seed), sizeof(seed));
+}
+
+bool InitRandom(const char* seed, size_t len) {
+ if (!g_rng->Init(seed, len)) {
+ LOG(LS_ERROR) << "Failed to init random generator!";
+ return false;
+ }
+ return true;
+}
+
+std::string CreateRandomString(size_t len) {
+ std::string str;
+ CreateRandomString(len, &str);
+ return str;
+}
+
+bool CreateRandomString(size_t len, std::string* str) {
+ str->clear();
+ scoped_array<uint8> bytes(new uint8[len]);
+ if (!g_rng->Generate(bytes.get(), len)) {
+ LOG(LS_ERROR) << "Failed to generate random string!";
+ return false;
+ }
+ str->reserve(len);
+ for (size_t i = 0; i < len; ++i) {
+ str->push_back(BASE64[bytes[i] & 63]);
+ }
+ return true;
+}
+
+uint32 CreateRandomId() {
+ uint32 id;
+ if (!g_rng->Generate(&id, sizeof(id))) {
+ LOG(LS_ERROR) << "Failed to generate random id!";
+ }
+ return id;
+}
+
+uint32 CreateRandomNonZeroId() {
+ uint32 id;
+ do {
+ id = CreateRandomId();
+ } while (id == 0);
+ return id;
+}
+
+} // namespace talk_base
diff --git a/talk/base/helpers.h b/talk/base/helpers.h
new file mode 100644
index 0000000..83a3c7f
--- /dev/null
+++ b/talk/base/helpers.h
@@ -0,0 +1,61 @@
+/*
+ * 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_HELPERS_H_
+#define TALK_BASE_HELPERS_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// For testing, we can return predictable data.
+void SetRandomTestMode(bool test);
+
+// Initializes the RNG, and seeds it with the specified entropy.
+bool InitRandom(int seed);
+bool InitRandom(const char* seed, size_t len);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// WARNING: could silently fail. Use the version below instead.
+std::string CreateRandomString(size_t length);
+
+// Generates a (cryptographically) random string of the given length.
+// We generate base64 values so that they will be printable.
+// Return false if the random number generator failed.
+bool CreateRandomString(size_t length, std::string* str);
+
+// Generates a random id.
+uint32 CreateRandomId();
+
+// Generates a random id > 0.
+uint32 CreateRandomNonZeroId();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HELPERS_H_
diff --git a/talk/base/host.cc b/talk/base/host.cc
new file mode 100644
index 0000000..7decc49
--- /dev/null
+++ b/talk/base/host.cc
@@ -0,0 +1,49 @@
+/*
+ * 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 "talk/base/host.h"
+
+#ifdef POSIX
+#include <sys/utsname.h>
+#endif // POSIX
+
+#include <string>
+
+namespace talk_base {
+
+std::string GetHostName() {
+ // TODO: fix or get rid of this
+#if 0
+ struct utsname nm;
+ if (uname(&nm) < 0)
+ FatalError("uname", LAST_SYSTEM_ERROR);
+ return std::string(nm.nodename);
+#endif
+ return "cricket";
+}
+
+} // namespace talk_base
diff --git a/talk/base/host.h b/talk/base/host.h
new file mode 100644
index 0000000..8528240
--- /dev/null
+++ b/talk/base/host.h
@@ -0,0 +1,40 @@
+/*
+ * 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_HOST_H_
+#define TALK_BASE_HOST_H_
+
+#include <string>
+
+namespace talk_base {
+
+// Returns the name of the local host.
+std::string GetHostName();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HOST_H_
diff --git a/talk/base/httpbase.cc b/talk/base/httpbase.cc
new file mode 100644
index 0000000..f5dc60e
--- /dev/null
+++ b/talk/base/httpbase.cc
@@ -0,0 +1,894 @@
+/*
+ * 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.
+ */
+
+// Copyright 2005 Google Inc. All Rights Reserved.
+//
+
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#else // !WIN32
+#define SEC_E_CERT_EXPIRED (-2146893016)
+#endif // !WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool MatchHeader(const char* str, size_t len, HttpHeader header) {
+ const char* const header_str = ToString(header);
+ const size_t header_len = strlen(header_str);
+ return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0);
+}
+
+enum {
+ MSG_READ
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpParser
+//////////////////////////////////////////////////////////////////////
+
+HttpParser::HttpParser() {
+ reset();
+}
+
+HttpParser::~HttpParser() {
+}
+
+void
+HttpParser::reset() {
+ state_ = ST_LEADER;
+ chunked_ = false;
+ data_size_ = SIZE_UNKNOWN;
+}
+
+HttpParser::ProcessResult
+HttpParser::Process(const char* buffer, size_t len, size_t* processed,
+ HttpError* error) {
+ *processed = 0;
+ *error = HE_NONE;
+
+ if (state_ >= ST_COMPLETE) {
+ ASSERT(false);
+ return PR_COMPLETE;
+ }
+
+ while (true) {
+ if (state_ < ST_DATA) {
+ size_t pos = *processed;
+ while ((pos < len) && (buffer[pos] != '\n')) {
+ pos += 1;
+ }
+ if (pos >= len) {
+ break; // don't have a full header
+ }
+ const char* line = buffer + *processed;
+ size_t len = (pos - *processed);
+ *processed = pos + 1;
+ while ((len > 0) && isspace(static_cast<unsigned char>(line[len-1]))) {
+ len -= 1;
+ }
+ ProcessResult result = ProcessLine(line, len, error);
+ LOG(LS_VERBOSE) << "Processed line, result=" << result;
+
+ if (PR_CONTINUE != result) {
+ return result;
+ }
+ } else if (data_size_ == 0) {
+ if (chunked_) {
+ state_ = ST_CHUNKTERM;
+ } else {
+ return PR_COMPLETE;
+ }
+ } else {
+ size_t available = len - *processed;
+ if (available <= 0) {
+ break; // no more data
+ }
+ if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) {
+ available = data_size_;
+ }
+ size_t read = 0;
+ ProcessResult result = ProcessData(buffer + *processed, available, read,
+ error);
+ LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: "
+ << read << " err: " << error;
+
+ if (PR_CONTINUE != result) {
+ return result;
+ }
+ *processed += read;
+ if (data_size_ != SIZE_UNKNOWN) {
+ data_size_ -= read;
+ }
+ }
+ }
+
+ return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) {
+ LOG_F(LS_VERBOSE) << " state: " << state_ << " line: "
+ << std::string(line, len) << " len: " << len << " err: "
+ << error;
+
+ switch (state_) {
+ case ST_LEADER:
+ state_ = ST_HEADERS;
+ return ProcessLeader(line, len, error);
+
+ case ST_HEADERS:
+ if (len > 0) {
+ const char* value = strchrn(line, len, ':');
+ if (!value) {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ }
+ size_t nlen = (value - line);
+ const char* eol = line + len;
+ do {
+ value += 1;
+ } while ((value < eol) && isspace(static_cast<unsigned char>(*value)));
+ size_t vlen = eol - value;
+ if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) {
+ unsigned int temp_size;
+ if (sscanf(value, "%u", &temp_size) != 1) {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ }
+ data_size_ = static_cast<size_t>(temp_size);
+ } else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) {
+ if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) {
+ chunked_ = true;
+ } else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) {
+ chunked_ = false;
+ } else {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ }
+ }
+ return ProcessHeader(line, nlen, value, vlen, error);
+ } else {
+ state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+ return ProcessHeaderComplete(chunked_, data_size_, error);
+ }
+ break;
+
+ case ST_CHUNKSIZE:
+ if (len > 0) {
+ char* ptr = NULL;
+ data_size_ = strtoul(line, &ptr, 16);
+ if (ptr != line + len) {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ }
+ state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
+ } else {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ }
+ break;
+
+ case ST_CHUNKTERM:
+ if (len > 0) {
+ *error = HE_PROTOCOL;
+ return PR_COMPLETE;
+ } else {
+ state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
+ }
+ break;
+
+ case ST_TRAILERS:
+ if (len == 0) {
+ return PR_COMPLETE;
+ }
+ // *error = onHttpRecvTrailer();
+ break;
+
+ default:
+ ASSERT(false);
+ break;
+ }
+
+ return PR_CONTINUE;
+}
+
+bool
+HttpParser::is_valid_end_of_input() const {
+ return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN);
+}
+
+void
+HttpParser::complete(HttpError error) {
+ if (state_ < ST_COMPLETE) {
+ state_ = ST_COMPLETE;
+ OnComplete(error);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase::DocumentStream
+//////////////////////////////////////////////////////////////////////
+
+class BlockingMemoryStream : public ExternalMemoryStream {
+public:
+ BlockingMemoryStream(char* buffer, size_t size)
+ : ExternalMemoryStream(buffer, size) { }
+
+ virtual StreamResult DoReserve(size_t size, int* error) {
+ return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK;
+ }
+};
+
+class HttpBase::DocumentStream : public StreamInterface {
+public:
+ DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { }
+
+ virtual StreamState GetState() const {
+ if (NULL == base_)
+ return SS_CLOSED;
+ if (HM_RECV == base_->mode_)
+ return SS_OPEN;
+ return SS_OPENING;
+ }
+
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (!base_) {
+ if (error) *error = error_;
+ return (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+ }
+
+ if (HM_RECV != base_->mode_) {
+ return SR_BLOCK;
+ }
+
+ // DoReceiveLoop writes http document data to the StreamInterface* document
+ // member of HttpData. In this case, we want this data to be written
+ // directly to our buffer. To accomplish this, we wrap our buffer with a
+ // StreamInterface, and replace the existing document with our wrapper.
+ // When the method returns, we restore the old document. Ideally, we would
+ // pass our StreamInterface* to DoReceiveLoop, but due to the callbacks
+ // of HttpParser, we would still need to store the pointer temporarily.
+ scoped_ptr<StreamInterface>
+ stream(new BlockingMemoryStream(reinterpret_cast<char*>(buffer),
+ buffer_len));
+
+ // Replace the existing document with our wrapped buffer.
+ base_->data_->document.swap(stream);
+
+ // Pump the I/O loop. DoReceiveLoop is guaranteed not to attempt to
+ // complete the I/O process, which means that our wrapper is not in danger
+ // of being deleted. To ensure this, DoReceiveLoop returns true when it
+ // wants complete to be called. We make sure to uninstall our wrapper
+ // before calling complete().
+ HttpError http_error;
+ bool complete = base_->DoReceiveLoop(&http_error);
+
+ // Reinstall the original output document.
+ base_->data_->document.swap(stream);
+
+ // If we reach the end of the receive stream, we disconnect our stream
+ // adapter from the HttpBase, and further calls to read will either return
+ // EOS or ERROR, appropriately. Finally, we call complete().
+ StreamResult result = SR_BLOCK;
+ if (complete) {
+ HttpBase* base = Disconnect(http_error);
+ if (error) *error = error_;
+ result = (HE_NONE == error_) ? SR_EOS : SR_ERROR;
+ base->complete(http_error);
+ }
+
+ // Even if we are complete, if some data was read we must return SUCCESS.
+ // Future Reads will return EOS or ERROR based on the error_ variable.
+ size_t position;
+ stream->GetPosition(&position);
+ if (position > 0) {
+ if (read) *read = position;
+ result = SR_SUCCESS;
+ }
+ return result;
+ }
+
+ 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() {
+ if (base_) {
+ HttpBase* base = Disconnect(HE_NONE);
+ if (HM_RECV == base->mode_ && base->http_stream_) {
+ // Read I/O could have been stalled on the user of this DocumentStream,
+ // so restart the I/O process now that we've removed ourselves.
+ base->http_stream_->PostEvent(SE_READ, 0);
+ }
+ }
+ }
+
+ virtual bool GetAvailable(size_t* size) const {
+ if (!base_ || HM_RECV != base_->mode_)
+ return false;
+ size_t data_size = base_->GetDataRemaining();
+ if (SIZE_UNKNOWN == data_size)
+ return false;
+ if (size)
+ *size = data_size;
+ return true;
+ }
+
+ HttpBase* Disconnect(HttpError error) {
+ ASSERT(NULL != base_);
+ ASSERT(NULL != base_->doc_stream_);
+ HttpBase* base = base_;
+ base_->doc_stream_ = NULL;
+ base_ = NULL;
+ error_ = error;
+ return base;
+ }
+
+private:
+ HttpBase* base_;
+ HttpError error_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpBase
+//////////////////////////////////////////////////////////////////////
+
+HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL),
+ http_stream_(NULL), doc_stream_(NULL) {
+}
+
+HttpBase::~HttpBase() {
+ ASSERT(HM_NONE == mode_);
+}
+
+bool
+HttpBase::isConnected() const {
+ return (http_stream_ != NULL) && (http_stream_->GetState() == SS_OPEN);
+}
+
+bool
+HttpBase::attach(StreamInterface* stream) {
+ if ((mode_ != HM_NONE) || (http_stream_ != NULL) || (stream == NULL)) {
+ ASSERT(false);
+ return false;
+ }
+ http_stream_ = stream;
+ http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent);
+ mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
+ return true;
+}
+
+StreamInterface*
+HttpBase::detach() {
+ ASSERT(HM_NONE == mode_);
+ if (mode_ != HM_NONE) {
+ return NULL;
+ }
+ StreamInterface* stream = http_stream_;
+ http_stream_ = NULL;
+ if (stream) {
+ stream->SignalEvent.disconnect(this);
+ }
+ return stream;
+}
+
+void
+HttpBase::send(HttpData* data) {
+ ASSERT(HM_NONE == mode_);
+ if (mode_ != HM_NONE) {
+ return;
+ } else if (!isConnected()) {
+ OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+ return;
+ }
+
+ mode_ = HM_SEND;
+ data_ = data;
+ len_ = 0;
+ ignore_data_ = chunk_data_ = false;
+
+ if (data_->document.get()) {
+ data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+ }
+
+ std::string encoding;
+ if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding)
+ && (encoding == "chunked")) {
+ chunk_data_ = true;
+ }
+
+ len_ = data_->formatLeader(buffer_, sizeof(buffer_));
+ len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+
+ header_ = data_->begin();
+ if (header_ == data_->end()) {
+ // We must call this at least once, in the case where there are no headers.
+ queue_headers();
+ }
+
+ flush_data();
+}
+
+void
+HttpBase::recv(HttpData* data) {
+ ASSERT(HM_NONE == mode_);
+ if (mode_ != HM_NONE) {
+ return;
+ } else if (!isConnected()) {
+ OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
+ return;
+ }
+
+ mode_ = HM_RECV;
+ data_ = data;
+ len_ = 0;
+ ignore_data_ = chunk_data_ = false;
+
+ reset();
+ if (doc_stream_) {
+ doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0);
+ } else {
+ read_and_process_data();
+ }
+}
+
+void
+HttpBase::abort(HttpError err) {
+ if (mode_ != HM_NONE) {
+ if (http_stream_ != NULL) {
+ http_stream_->Close();
+ }
+ do_complete(err);
+ }
+}
+
+StreamInterface* HttpBase::GetDocumentStream() {
+ if (doc_stream_)
+ return NULL;
+ doc_stream_ = new DocumentStream(this);
+ return doc_stream_;
+}
+
+HttpError HttpBase::HandleStreamClose(int error) {
+ if (http_stream_ != NULL) {
+ http_stream_->Close();
+ }
+ if (error == 0) {
+ if ((mode_ == HM_RECV) && is_valid_end_of_input()) {
+ return HE_NONE;
+ } else {
+ return HE_DISCONNECTED;
+ }
+ } else if (error == SOCKET_EACCES) {
+ return HE_AUTH;
+ } else if (error == SEC_E_CERT_EXPIRED) {
+ return HE_CERTIFICATE_EXPIRED;
+ }
+ LOG_F(LS_ERROR) << "(" << error << ")";
+ return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR;
+}
+
+bool HttpBase::DoReceiveLoop(HttpError* error) {
+ ASSERT(HM_RECV == mode_);
+ ASSERT(NULL != error);
+
+ // Do to the latency between receiving read notifications from
+ // pseudotcpchannel, we rely on repeated calls to read in order to acheive
+ // ideal throughput. The number of reads is limited to prevent starving
+ // the caller.
+
+ size_t loop_count = 0;
+ const size_t kMaxReadCount = 20;
+ bool process_requires_more_data = false;
+ do {
+ // The most frequent use of this function is response to new data available
+ // on http_stream_. Therefore, we optimize by attempting to read from the
+ // network first (as opposed to processing existing data first).
+
+ if (len_ < sizeof(buffer_)) {
+ // Attempt to buffer more data.
+ size_t read;
+ int read_error;
+ StreamResult read_result = http_stream_->Read(buffer_ + len_,
+ sizeof(buffer_) - len_,
+ &read, &read_error);
+ switch (read_result) {
+ case SR_SUCCESS:
+ ASSERT(len_ + read <= sizeof(buffer_));
+ len_ += read;
+ break;
+ case SR_BLOCK:
+ if (process_requires_more_data) {
+ // We're can't make progress until more data is available.
+ return false;
+ }
+ // Attempt to process the data already in our buffer.
+ break;
+ case SR_EOS:
+ // Clean close, with no error. Fall through to HandleStreamClose.
+ read_error = 0;
+ case SR_ERROR:
+ *error = HandleStreamClose(read_error);
+ return true;
+ }
+ } else if (process_requires_more_data) {
+ // We have too much unprocessed data in our buffer. This should only
+ // occur when a single HTTP header is longer than the buffer size (32K).
+ // Anything longer than that is almost certainly an error.
+ *error = HE_OVERFLOW;
+ return true;
+ }
+
+ // Process data in our buffer. Process is not guaranteed to process all
+ // the buffered data. In particular, it will wait until a complete
+ // protocol element (such as http header, or chunk size) is available,
+ // before processing it in its entirety. Also, it is valid and sometimes
+ // necessary to call Process with an empty buffer, since the state machine
+ // may have interrupted state transitions to complete.
+ size_t processed;
+ ProcessResult process_result = Process(buffer_, len_, &processed,
+ error);
+ ASSERT(processed <= len_);
+ len_ -= processed;
+ memmove(buffer_, buffer_ + processed, len_);
+ switch (process_result) {
+ case PR_CONTINUE:
+ // We need more data to make progress.
+ process_requires_more_data = true;
+ break;
+ case PR_BLOCK:
+ // We're stalled on writing the processed data.
+ return false;
+ case PR_COMPLETE:
+ // *error already contains the correct code.
+ return true;
+ }
+ } while (++loop_count <= kMaxReadCount);
+
+ LOG_F(LS_WARNING) << "danger of starvation";
+ return false;
+}
+
+void
+HttpBase::read_and_process_data() {
+ HttpError error;
+ if (DoReceiveLoop(&error)) {
+ complete(error);
+ }
+}
+
+void
+HttpBase::flush_data() {
+ ASSERT(HM_SEND == mode_);
+
+ // When send_required is true, no more buffering can occur without a network
+ // write.
+ bool send_required = (len_ >= sizeof(buffer_));
+
+ while (true) {
+ ASSERT(len_ <= sizeof(buffer_));
+
+ // HTTP is inherently sensitive to round trip latency, since a frequent use
+ // case is for small requests and responses to be sent back and forth, and
+ // the lack of pipelining forces a single request to take a minimum of the
+ // round trip time. As a result, it is to our benefit to pack as much data
+ // into each packet as possible. Thus, we defer network writes until we've
+ // buffered as much data as possible.
+
+ if (!send_required && (header_ != data_->end())) {
+ // First, attempt to queue more header data.
+ send_required = queue_headers();
+ }
+
+ if (!send_required && (NULL != data_->document.get())) {
+ // Next, attempt to queue document data.
+
+ const size_t kChunkDigits = 8;
+ size_t offset, reserve;
+ if (chunk_data_) {
+ // Reserve characters at the start for X-byte hex value and \r\n
+ offset = len_ + kChunkDigits + 2;
+ // ... and 2 characters at the end for \r\n
+ reserve = offset + 2;
+ } else {
+ offset = len_;
+ reserve = offset;
+ }
+
+ if (reserve >= sizeof(buffer_)) {
+ send_required = true;
+ } else {
+ size_t read;
+ int error;
+ StreamResult result = data_->document->Read(buffer_ + offset,
+ sizeof(buffer_) - reserve,
+ &read, &error);
+ if (result == SR_SUCCESS) {
+ ASSERT(reserve + read <= sizeof(buffer_));
+ if (chunk_data_) {
+ // Prepend the chunk length in hex.
+ // Note: sprintfn appends a null terminator, which is why we can't
+ // combine it with the line terminator.
+ sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x",
+ kChunkDigits, read);
+ // Add line terminator to the chunk length.
+ memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2);
+ // Add line terminator to the end of the chunk.
+ memcpy(buffer_ + offset + read, "\r\n", 2);
+ }
+ len_ = reserve + read;
+ } else if (result == SR_BLOCK) {
+ // Nothing to do but flush data to the network.
+ send_required = true;
+ } else if (result == SR_EOS) {
+ if (chunk_data_) {
+ // Append the empty chunk and empty trailers, then turn off
+ // chunking.
+ ASSERT(len_ + 5 <= sizeof(buffer_));
+ memcpy(buffer_ + len_, "0\r\n\r\n", 5);
+ len_ += 5;
+ chunk_data_ = false;
+ } else if (0 == len_) {
+ // No more data to read, and no more data to write.
+ do_complete();
+ return;
+ }
+ // Although we are done reading data, there is still data which needs
+ // to be flushed to the network.
+ send_required = true;
+ } else {
+ LOG_F(LS_ERROR) << "Read error: " << error;
+ do_complete(HE_STREAM);
+ return;
+ }
+ }
+ }
+
+ if (0 == len_) {
+ // No data currently available to send.
+ if (NULL == data_->document.get()) {
+ // If there is no source document, that means we're done.
+ do_complete();
+ }
+ return;
+ }
+
+ size_t written;
+ int error;
+ StreamResult result = http_stream_->Write(buffer_, len_, &written, &error);
+ if (result == SR_SUCCESS) {
+ ASSERT(written <= len_);
+ len_ -= written;
+ memmove(buffer_, buffer_ + written, len_);
+ send_required = false;
+ } else if (result == SR_BLOCK) {
+ if (send_required) {
+ // Nothing more we can do until network is writeable.
+ return;
+ }
+ } else {
+ ASSERT(result == SR_ERROR);
+ LOG_F(LS_ERROR) << "error";
+ OnHttpStreamEvent(http_stream_, SE_CLOSE, error);
+ return;
+ }
+ }
+
+ ASSERT(false);
+}
+
+bool
+HttpBase::queue_headers() {
+ ASSERT(HM_SEND == mode_);
+ while (header_ != data_->end()) {
+ size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_,
+ "%.*s: %.*s\r\n",
+ header_->first.size(), header_->first.data(),
+ header_->second.size(), header_->second.data());
+ if (len_ + len < sizeof(buffer_) - 3) {
+ len_ += len;
+ ++header_;
+ } else if (len_ == 0) {
+ LOG(WARNING) << "discarding header that is too long: " << header_->first;
+ ++header_;
+ } else {
+ // Not enough room for the next header, write to network first.
+ return true;
+ }
+ }
+ // End of headers
+ len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
+ return false;
+}
+
+void
+HttpBase::do_complete(HttpError err) {
+ ASSERT(mode_ != HM_NONE);
+ HttpMode mode = mode_;
+ mode_ = HM_NONE;
+ if (data_ && data_->document.get()) {
+ data_->document->SignalEvent.disconnect(this);
+ }
+ data_ = NULL;
+ if ((HM_RECV == mode) && doc_stream_) {
+ ASSERT(HE_NONE != err); // We should have Disconnected doc_stream_ already.
+ DocumentStream* ds = doc_stream_;
+ ds->Disconnect(err);
+ ds->SignalEvent(ds, SE_CLOSE, err);
+ }
+ if (notify_) {
+ notify_->onHttpComplete(mode, err);
+ }
+}
+
+//
+// Stream Signals
+//
+
+void
+HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) {
+ ASSERT(stream == http_stream_);
+ if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) {
+ do_complete();
+ return;
+ }
+
+ if ((events & SE_WRITE) && (mode_ == HM_SEND)) {
+ flush_data();
+ return;
+ }
+
+ if ((events & SE_READ) && (mode_ == HM_RECV)) {
+ if (doc_stream_) {
+ doc_stream_->SignalEvent(doc_stream_, SE_READ, 0);
+ } else {
+ read_and_process_data();
+ }
+ return;
+ }
+
+ if ((events & SE_CLOSE) == 0)
+ return;
+
+ HttpError http_error = HandleStreamClose(error);
+ if (mode_ == HM_RECV) {
+ complete(http_error);
+ } else if (mode_ != HM_NONE) {
+ do_complete(http_error);
+ } else if (notify_) {
+ notify_->onHttpClosed(http_error);
+ }
+}
+
+void
+HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) {
+ ASSERT(stream == data_->document.get());
+ if ((events & SE_WRITE) && (mode_ == HM_RECV)) {
+ read_and_process_data();
+ return;
+ }
+
+ if ((events & SE_READ) && (mode_ == HM_SEND)) {
+ flush_data();
+ return;
+ }
+
+ if (events & SE_CLOSE) {
+ LOG_F(LS_ERROR) << "Read error: " << error;
+ do_complete(HE_STREAM);
+ return;
+ }
+}
+
+//
+// HttpParser Implementation
+//
+
+HttpParser::ProcessResult
+HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) {
+ *error = data_->parseLeader(line, len);
+ return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value,
+ size_t vlen, HttpError* error) {
+ std::string sname(name, nlen), svalue(value, vlen);
+ data_->addHeader(sname, svalue);
+ return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size,
+ HttpError* error) {
+ StreamInterface* old_docstream = doc_stream_;
+ if (notify_) {
+ *error = notify_->onHttpHeaderComplete(chunked, data_size);
+ // The request must not be aborted as a result of this callback.
+ ASSERT(NULL != data_);
+ }
+ if ((HE_NONE == *error) && (NULL != data_->document.get())) {
+ data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
+ }
+ if (HE_NONE != *error) {
+ return PR_COMPLETE;
+ }
+ if (old_docstream != doc_stream_) {
+ // Break out of Process loop, since our I/O model just changed.
+ return PR_BLOCK;
+ }
+ return PR_CONTINUE;
+}
+
+HttpParser::ProcessResult
+HttpBase::ProcessData(const char* data, size_t len, size_t& read,
+ HttpError* error) {
+ LOG_F(LS_VERBOSE) << "data: " << std::string(data, len);
+ if (ignore_data_ || !data_->document.get()) {
+ read = len;
+ return PR_CONTINUE;
+ }
+ int write_error = 0;
+ switch (data_->document->Write(data, len, &read, &write_error)) {
+ case SR_SUCCESS:
+ return PR_CONTINUE;
+ case SR_BLOCK:
+ return PR_BLOCK;
+ case SR_EOS:
+ LOG_F(LS_ERROR) << "Unexpected EOS";
+ *error = HE_STREAM;
+ return PR_COMPLETE;
+ case SR_ERROR:
+ default:
+ LOG_F(LS_ERROR) << "Write error: " << write_error;
+ *error = HE_STREAM;
+ return PR_COMPLETE;
+ }
+}
+
+void
+HttpBase::OnComplete(HttpError err) {
+ LOG_F(LS_VERBOSE);
+ do_complete(err);
+}
+
+} // namespace talk_base
diff --git a/talk/base/httpbase.h b/talk/base/httpbase.h
new file mode 100644
index 0000000..97527eb
--- /dev/null
+++ b/talk/base/httpbase.h
@@ -0,0 +1,201 @@
+/*
+ * 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.
+ */
+
+// Copyright 2005 Google Inc. All Rights Reserved.
+//
+
+
+#ifndef TALK_BASE_HTTPBASE_H__
+#define TALK_BASE_HTTPBASE_H__
+
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and
+// generates events for:
+// Structural Elements: Leader, Headers, Document Data
+// Events: End of Headers, End of Document, Errors
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpParser {
+public:
+ enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE };
+ HttpParser();
+ virtual ~HttpParser();
+
+ void reset();
+ ProcessResult Process(const char* buffer, size_t len, size_t* processed,
+ HttpError* error);
+ bool is_valid_end_of_input() const;
+ void complete(HttpError err);
+
+ size_t GetDataRemaining() const { return data_size_; }
+
+protected:
+ ProcessResult ProcessLine(const char* line, size_t len, HttpError* error);
+
+ // HttpParser Interface
+ virtual ProcessResult ProcessLeader(const char* line, size_t len,
+ HttpError* error) = 0;
+ virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+ const char* value, size_t vlen,
+ HttpError* error) = 0;
+ virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+ HttpError* error) = 0;
+ virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+ HttpError* error) = 0;
+ virtual void OnComplete(HttpError err) = 0;
+
+private:
+ enum State {
+ ST_LEADER, ST_HEADERS,
+ ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS,
+ ST_DATA, ST_COMPLETE
+ } state_;
+ bool chunked_;
+ size_t data_size_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// IHttpNotify
+///////////////////////////////////////////////////////////////////////////////
+
+enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND };
+
+class IHttpNotify {
+public:
+ virtual ~IHttpNotify() {}
+ virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0;
+ virtual void onHttpComplete(HttpMode mode, HttpError err) = 0;
+ virtual void onHttpClosed(HttpError err) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpBase - Provides a state machine for implementing HTTP-based components.
+// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP
+// stream, and then call send() or recv() to initiate sending or receiving one
+// side of an HTTP transaction. By default, HttpBase operates as an I/O pump,
+// moving data from the HTTP stream to the HttpData object and vice versa.
+// However, it can also operate in stream mode, in which case the user of the
+// stream interface drives I/O via calls to Read().
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpBase
+: private HttpParser,
+ public sigslot::has_slots<>
+{
+public:
+ HttpBase();
+ virtual ~HttpBase();
+
+ void notify(IHttpNotify* notify) { notify_ = notify; }
+ bool attach(StreamInterface* stream);
+ StreamInterface* stream() { return http_stream_; }
+ StreamInterface* detach();
+ bool isConnected() const;
+
+ void send(HttpData* data);
+ void recv(HttpData* data);
+ void abort(HttpError err);
+
+ HttpMode mode() const { return mode_; }
+
+ void set_ignore_data(bool ignore) { ignore_data_ = ignore; }
+ bool ignore_data() const { return ignore_data_; }
+
+ // Obtaining this stream puts HttpBase into stream mode until the stream
+ // is closed. HttpBase can only expose one open stream interface at a time.
+ // Further calls will return NULL.
+ StreamInterface* GetDocumentStream();
+
+protected:
+ // Do cleanup when the http stream closes (error may be 0 for a clean
+ // shutdown), and return the error code to signal.
+ HttpError HandleStreamClose(int error);
+
+ // DoReceiveLoop acts as a data pump, pulling data from the http stream,
+ // pushing it through the HttpParser, and then populating the HttpData object
+ // based on the callbacks from the parser. One of the most interesting
+ // callbacks is ProcessData, which provides the actual http document body.
+ // This data is then written to the HttpData::document. As a result, data
+ // flows from the network to the document, with some incidental protocol
+ // parsing in between.
+ // Ideally, we would pass in the document* to DoReceiveLoop, to more easily
+ // support GetDocumentStream(). However, since the HttpParser is callback
+ // driven, we are forced to store the pointer somewhere until the callback
+ // is triggered.
+ // Returns true if the received document has finished, and
+ // HttpParser::complete should be called.
+ bool DoReceiveLoop(HttpError* err);
+
+ void read_and_process_data();
+ void flush_data();
+ bool queue_headers();
+ void do_complete(HttpError err = HE_NONE);
+
+ void OnHttpStreamEvent(StreamInterface* stream, int events, int error);
+ void OnDocumentEvent(StreamInterface* stream, int events, int error);
+
+ // HttpParser Interface
+ virtual ProcessResult ProcessLeader(const char* line, size_t len,
+ HttpError* error);
+ virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
+ const char* value, size_t vlen,
+ HttpError* error);
+ virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
+ HttpError* error);
+ virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
+ HttpError* error);
+ virtual void OnComplete(HttpError err);
+
+private:
+ class DocumentStream;
+ friend class DocumentStream;
+
+ enum { kBufferSize = 32 * 1024 };
+
+ HttpMode mode_;
+ HttpData* data_;
+ IHttpNotify* notify_;
+ StreamInterface* http_stream_;
+ DocumentStream* doc_stream_;
+ char buffer_[kBufferSize];
+ size_t len_;
+
+ bool ignore_data_, chunk_data_;
+ HttpData::const_iterator header_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPBASE_H__
diff --git a/talk/base/httpclient.cc b/talk/base/httpclient.cc
new file mode 100644
index 0000000..3b6e97e
--- /dev/null
+++ b/talk/base/httpclient.cc
@@ -0,0 +1,813 @@
+/*
+ * 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 <time.h>
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/diskcache.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Helpers
+//////////////////////////////////////////////////////////////////////
+
+namespace {
+
+const size_t kCacheHeader = 0;
+const size_t kCacheBody = 1;
+
+// Convert decimal string to integer
+bool HttpStringToInt(const std::string& str, unsigned long* val) {
+ ASSERT(NULL != val);
+ char* eos = NULL;
+ *val = strtoul(str.c_str(), &eos, 10);
+ return (*eos == '\0');
+}
+
+bool HttpShouldCache(const HttpTransaction& t) {
+ bool verb_allows_cache = (t.request.verb == HV_GET)
+ || (t.request.verb == HV_HEAD);
+ bool is_range_response = t.response.hasHeader(HH_CONTENT_RANGE, NULL);
+ bool has_expires = t.response.hasHeader(HH_EXPIRES, NULL);
+ bool request_allows_cache =
+ has_expires || (std::string::npos != t.request.path.find('?'));
+ bool response_allows_cache =
+ has_expires || HttpCodeIsCacheable(t.response.scode);
+
+ bool may_cache = verb_allows_cache
+ && request_allows_cache
+ && response_allows_cache
+ && !is_range_response;
+
+ std::string value;
+ if (t.response.hasHeader(HH_CACHE_CONTROL, &value)) {
+ HttpAttributeList directives;
+ HttpParseAttributes(value.data(), value.size(), directives);
+ // Response Directives Summary:
+ // public - always cacheable
+ // private - do not cache in a shared cache
+ // no-cache - may cache, but must revalidate whether fresh or stale
+ // no-store - sensitive information, do not cache or store in any way
+ // max-age - supplants Expires for staleness
+ // s-maxage - use as max-age for shared caches, ignore otherwise
+ // must-revalidate - may cache, but must revalidate after stale
+ // proxy-revalidate - shared cache must revalidate
+ if (HttpHasAttribute(directives, "no-store", NULL)) {
+ may_cache = false;
+ } else if (HttpHasAttribute(directives, "public", NULL)) {
+ may_cache = true;
+ }
+ }
+ return may_cache;
+}
+
+enum HttpCacheState {
+ HCS_FRESH, // In cache, may use
+ HCS_STALE, // In cache, must revalidate
+ HCS_NONE // Not in cache
+};
+
+HttpCacheState HttpGetCacheState(const HttpTransaction& t) {
+ // Temporaries
+ std::string s_temp;
+ unsigned long i_temp;
+
+ // Current time
+ unsigned long now = time(0);
+
+ HttpAttributeList cache_control;
+ if (t.response.hasHeader(HH_CACHE_CONTROL, &s_temp)) {
+ HttpParseAttributes(s_temp.data(), s_temp.size(), cache_control);
+ }
+
+ // Compute age of cache document
+ unsigned long date;
+ if (!t.response.hasHeader(HH_DATE, &s_temp)
+ || !HttpDateToSeconds(s_temp, &date))
+ return HCS_NONE;
+
+ // TODO: Timestamp when cache request sent and response received?
+ unsigned long request_time = date;
+ unsigned long response_time = date;
+
+ unsigned long apparent_age = 0;
+ if (response_time > date) {
+ apparent_age = response_time - date;
+ }
+
+ unsigned long corrected_received_age = apparent_age;
+ if (t.response.hasHeader(HH_AGE, &s_temp)
+ && HttpStringToInt(s_temp, &i_temp)) {
+ corrected_received_age = stdmax(apparent_age, i_temp);
+ }
+
+ unsigned long response_delay = response_time - request_time;
+ unsigned long corrected_initial_age = corrected_received_age + response_delay;
+ unsigned long resident_time = now - response_time;
+ unsigned long current_age = corrected_initial_age + resident_time;
+
+ // Compute lifetime of document
+ unsigned long lifetime;
+ if (HttpHasAttribute(cache_control, "max-age", &s_temp)) {
+ lifetime = atoi(s_temp.c_str());
+ } else if (t.response.hasHeader(HH_EXPIRES, &s_temp)
+ && HttpDateToSeconds(s_temp, &i_temp)) {
+ lifetime = i_temp - date;
+ } else if (t.response.hasHeader(HH_LAST_MODIFIED, &s_temp)
+ && HttpDateToSeconds(s_temp, &i_temp)) {
+ // TODO: Issue warning 113 if age > 24 hours
+ lifetime = (now - i_temp) / 10;
+ } else {
+ return HCS_STALE;
+ }
+
+ return (lifetime > current_age) ? HCS_FRESH : HCS_STALE;
+}
+
+enum HttpValidatorStrength {
+ HVS_NONE,
+ HVS_WEAK,
+ HVS_STRONG
+};
+
+HttpValidatorStrength
+HttpRequestValidatorLevel(const HttpRequestData& request) {
+ if (HV_GET != request.verb)
+ return HVS_STRONG;
+ return request.hasHeader(HH_RANGE, NULL) ? HVS_STRONG : HVS_WEAK;
+}
+
+HttpValidatorStrength
+HttpResponseValidatorLevel(const HttpResponseData& response) {
+ std::string value;
+ if (response.hasHeader(HH_ETAG, &value)) {
+ bool is_weak = (strnicmp(value.c_str(), "W/", 2) == 0);
+ return is_weak ? HVS_WEAK : HVS_STRONG;
+ }
+ if (response.hasHeader(HH_LAST_MODIFIED, &value)) {
+ unsigned long last_modified, date;
+ if (HttpDateToSeconds(value, &last_modified)
+ && response.hasHeader(HH_DATE, &value)
+ && HttpDateToSeconds(value, &date)
+ && (last_modified + 60 < date)) {
+ return HVS_STRONG;
+ }
+ return HVS_WEAK;
+ }
+ return HVS_NONE;
+}
+
+std::string GetCacheID(const HttpRequestData& request) {
+ std::string id, url;
+ id.append(ToString(request.verb));
+ id.append("_");
+ request.getAbsoluteUri(&url);
+ id.append(url);
+ return id;
+}
+
+} // anonymous namespace
+
+//////////////////////////////////////////////////////////////////////
+// Public Helpers
+//////////////////////////////////////////////////////////////////////
+
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+ StreamInterface* output, size_t* size) {
+ size_t length = 0;
+ // Write all unknown and end-to-end headers to a cache file
+ for (HttpData::const_iterator it = response->begin();
+ it != response->end(); ++it) {
+ HttpHeader header;
+ if (FromString(header, it->first) && !HttpHeaderIsEndToEnd(header))
+ continue;
+ length += it->first.length() + 2 + it->second.length() + 2;
+ if (!output)
+ continue;
+ std::string formatted_header(it->first);
+ formatted_header.append(": ");
+ formatted_header.append(it->second);
+ formatted_header.append("\r\n");
+ StreamResult result = output->WriteAll(formatted_header.data(),
+ formatted_header.length(),
+ NULL, NULL);
+ if (SR_SUCCESS != result) {
+ return false;
+ }
+ }
+ if (output && (SR_SUCCESS != output->WriteAll("\r\n", 2, NULL, NULL))) {
+ return false;
+ }
+ length += 2;
+ if (size)
+ *size = length;
+ return true;
+}
+
+bool HttpReadCacheHeaders(StreamInterface* input, HttpResponseData* response,
+ HttpData::HeaderCombine combine) {
+ while (true) {
+ std::string formatted_header;
+ StreamResult result = input->ReadLine(&formatted_header);
+ if ((SR_EOS == result) || (1 == formatted_header.size())) {
+ break;
+ }
+ if (SR_SUCCESS != result) {
+ return false;
+ }
+ size_t end_of_name = formatted_header.find(':');
+ if (std::string::npos == end_of_name) {
+ LOG_F(LS_WARNING) << "Malformed cache header";
+ continue;
+ }
+ size_t start_of_value = end_of_name + 1;
+ size_t end_of_value = formatted_header.length();
+ while ((start_of_value < end_of_value)
+ && isspace(formatted_header[start_of_value]))
+ ++start_of_value;
+ while ((start_of_value < end_of_value)
+ && isspace(formatted_header[end_of_value-1]))
+ --end_of_value;
+ size_t value_length = end_of_value - start_of_value;
+
+ std::string name(formatted_header.substr(0, end_of_name));
+ std::string value(formatted_header.substr(start_of_value, value_length));
+ response->changeHeader(name, value, combine);
+ }
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+//////////////////////////////////////////////////////////////////////
+
+const size_t kDefaultRetries = 1;
+const size_t kMaxRedirects = 5;
+
+HttpClient::HttpClient(const std::string& agent, StreamPool* pool,
+ HttpTransaction* transaction)
+ : agent_(agent), pool_(pool),
+ transaction_(transaction), free_transaction_(false),
+ retries_(kDefaultRetries), attempt_(0), redirects_(0),
+ redirect_action_(REDIRECT_DEFAULT),
+ uri_form_(URI_DEFAULT), cache_(NULL), cache_state_(CS_READY) {
+ base_.notify(this);
+ if (NULL == transaction_) {
+ free_transaction_ = true;
+ transaction_ = new HttpTransaction;
+ }
+}
+
+HttpClient::~HttpClient() {
+ base_.notify(NULL);
+ base_.abort(HE_SHUTDOWN);
+ release();
+ if (free_transaction_)
+ delete transaction_;
+}
+
+void HttpClient::reset() {
+ server_.Clear();
+ request().clear(true);
+ response().clear(true);
+ context_.reset();
+ redirects_ = 0;
+ base_.abort(HE_OPERATION_CANCELLED);
+}
+
+void HttpClient::set_server(const SocketAddress& address) {
+ server_ = address;
+ // Setting 'Host' here allows it to be overridden before starting the request,
+ // if necessary.
+ request().setHeader(HH_HOST, HttpAddress(server_, false), true);
+}
+
+StreamInterface* HttpClient::GetDocumentStream() {
+ return base_.GetDocumentStream();
+}
+
+void HttpClient::start() {
+ if (base_.mode() != HM_NONE) {
+ // call reset() to abort an in-progress request
+ ASSERT(false);
+ return;
+ }
+
+ ASSERT(!IsCacheActive());
+
+ if (request().hasHeader(HH_TRANSFER_ENCODING, NULL)) {
+ // Exact size must be known on the client. Instead of using chunked
+ // encoding, wrap data with auto-caching file or memory stream.
+ ASSERT(false);
+ return;
+ }
+
+ attempt_ = 0;
+
+ // If no content has been specified, using length of 0.
+ request().setHeader(HH_CONTENT_LENGTH, "0", false);
+
+ if (!agent_.empty()) {
+ request().setHeader(HH_USER_AGENT, agent_, false);
+ }
+
+ UriForm uri_form = uri_form_;
+ if (PROXY_HTTPS == proxy_.type) {
+ // Proxies require absolute form
+ uri_form = URI_ABSOLUTE;
+ request().version = HVER_1_0;
+ request().setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false);
+ } else {
+ request().setHeader(HH_CONNECTION, "Keep-Alive", false);
+ }
+
+ if (URI_ABSOLUTE == uri_form) {
+ // Convert to absolute uri form
+ std::string url;
+ if (request().getAbsoluteUri(&url)) {
+ request().path = url;
+ } else {
+ LOG(LS_WARNING) << "Couldn't obtain absolute uri";
+ }
+ } else if (URI_RELATIVE == uri_form) {
+ // Convert to relative uri form
+ std::string host, path;
+ if (request().getRelativeUri(&host, &path)) {
+ request().setHeader(HH_HOST, host);
+ request().path = path;
+ } else {
+ LOG(LS_WARNING) << "Couldn't obtain relative uri";
+ }
+ }
+
+ if ((NULL != cache_) && CheckCache()) {
+ return;
+ }
+
+ connect();
+}
+
+void HttpClient::connect() {
+ int stream_err;
+ StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err);
+ if (stream == NULL) {
+ ASSERT(0 != stream_err);
+ LOG(LS_ERROR) << "RequestConnectedStream error: " << stream_err;
+ onHttpComplete(HM_CONNECT, HE_CONNECT_FAILED);
+ } else {
+ base_.attach(stream);
+ if (stream->GetState() == SS_OPEN) {
+ base_.send(&transaction_->request);
+ }
+ }
+}
+
+void HttpClient::prepare_get(const std::string& url) {
+ reset();
+ Url<char> purl(url);
+ set_server(SocketAddress(purl.host(), purl.port()));
+ request().verb = HV_GET;
+ request().path = purl.full_path();
+}
+
+void HttpClient::prepare_post(const std::string& url,
+ const std::string& content_type,
+ StreamInterface* request_doc) {
+ reset();
+ Url<char> purl(url);
+ set_server(SocketAddress(purl.host(), purl.port()));
+ request().verb = HV_POST;
+ request().path = purl.full_path();
+ request().setContent(content_type, request_doc);
+}
+
+void HttpClient::release() {
+ if (StreamInterface* stream = base_.detach()) {
+ pool_->ReturnConnectedStream(stream);
+ }
+}
+
+bool HttpClient::ShouldRedirect(std::string* location) const {
+ // TODO: Unittest redirection.
+ if ((REDIRECT_NEVER == redirect_action_)
+ || !HttpCodeIsRedirection(response().scode)
+ || !response().hasHeader(HH_LOCATION, location)
+ || (redirects_ >= kMaxRedirects))
+ return false;
+ return (REDIRECT_ALWAYS == redirect_action_)
+ || (HC_SEE_OTHER == response().scode)
+ || (HV_HEAD == request().verb)
+ || (HV_GET == request().verb);
+}
+
+bool HttpClient::BeginCacheFile() {
+ ASSERT(NULL != cache_);
+ ASSERT(CS_READY == cache_state_);
+
+ std::string id = GetCacheID(request());
+ CacheLock lock(cache_, id, true);
+ if (!lock.IsLocked()) {
+ LOG_F(LS_WARNING) << "Couldn't lock cache";
+ return false;
+ }
+
+ if (HE_NONE != WriteCacheHeaders(id)) {
+ return false;
+ }
+
+ scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheBody));
+ if (!stream.get()) {
+ LOG_F(LS_ERROR) << "Couldn't open body cache";
+ return false;
+ }
+ lock.Commit();
+
+ // Let's secretly replace the response document with Folgers Crystals,
+ // er, StreamTap, so that we can mirror the data to our cache.
+ StreamInterface* output = response().document.release();
+ if (!output) {
+ output = new NullStream;
+ }
+ StreamTap* tap = new StreamTap(output, stream.release());
+ response().document.reset(tap);
+ return true;
+}
+
+HttpError HttpClient::WriteCacheHeaders(const std::string& id) {
+ scoped_ptr<StreamInterface> stream(cache_->WriteResource(id, kCacheHeader));
+ if (!stream.get()) {
+ LOG_F(LS_ERROR) << "Couldn't open header cache";
+ return HE_CACHE;
+ }
+
+ if (!HttpWriteCacheHeaders(&transaction_->response, stream.get(), NULL)) {
+ LOG_F(LS_ERROR) << "Couldn't write header cache";
+ return HE_CACHE;
+ }
+
+ return HE_NONE;
+}
+
+void HttpClient::CompleteCacheFile() {
+ // Restore previous response document
+ StreamTap* tap = static_cast<StreamTap*>(response().document.release());
+ response().document.reset(tap->Detach());
+
+ int error;
+ StreamResult result = tap->GetTapResult(&error);
+
+ // Delete the tap and cache stream (which completes cache unlock)
+ delete tap;
+
+ if (SR_SUCCESS != result) {
+ LOG(LS_ERROR) << "Cache file error: " << error;
+ cache_->DeleteResource(GetCacheID(request()));
+ }
+}
+
+bool HttpClient::CheckCache() {
+ ASSERT(NULL != cache_);
+ ASSERT(CS_READY == cache_state_);
+
+ std::string id = GetCacheID(request());
+ if (!cache_->HasResource(id)) {
+ // No cache file available
+ return false;
+ }
+
+ HttpError error = ReadCacheHeaders(id, true);
+
+ if (HE_NONE == error) {
+ switch (HttpGetCacheState(*transaction_)) {
+ case HCS_FRESH:
+ // Cache content is good, read from cache
+ break;
+ case HCS_STALE:
+ // Cache content may be acceptable. Issue a validation request.
+ if (PrepareValidate()) {
+ return false;
+ }
+ // Couldn't validate, fall through.
+ case HCS_NONE:
+ // Cache content is not useable. Issue a regular request.
+ response().clear(false);
+ return false;
+ }
+ }
+
+ if (HE_NONE == error) {
+ error = ReadCacheBody(id);
+ cache_state_ = CS_READY;
+ }
+
+ if (HE_CACHE == error) {
+ LOG_F(LS_WARNING) << "Cache failure, continuing with normal request";
+ response().clear(false);
+ return false;
+ }
+
+ SignalHttpClientComplete(this, error);
+ return true;
+}
+
+HttpError HttpClient::ReadCacheHeaders(const std::string& id, bool override) {
+ scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheHeader));
+ if (!stream.get()) {
+ return HE_CACHE;
+ }
+
+ HttpData::HeaderCombine combine =
+ override ? HttpData::HC_REPLACE : HttpData::HC_AUTO;
+
+ if (!HttpReadCacheHeaders(stream.get(), &transaction_->response, combine)) {
+ LOG_F(LS_ERROR) << "Error reading cache headers";
+ return HE_CACHE;
+ }
+
+ response().scode = HC_OK;
+ return HE_NONE;
+}
+
+HttpError HttpClient::ReadCacheBody(const std::string& id) {
+ cache_state_ = CS_READING;
+
+ HttpError error = HE_NONE;
+
+ size_t data_size;
+ scoped_ptr<StreamInterface> stream(cache_->ReadResource(id, kCacheBody));
+ if (!stream.get() || !stream->GetAvailable(&data_size)) {
+ LOG_F(LS_ERROR) << "Unavailable cache body";
+ error = HE_CACHE;
+ } else {
+ error = OnHeaderAvailable(false, false, data_size);
+ }
+
+ if ((HE_NONE == error)
+ && (HV_HEAD != request().verb)
+ && (NULL != response().document.get())) {
+ char buffer[1024 * 64];
+ StreamResult result = Flow(stream.get(), buffer, ARRAY_SIZE(buffer),
+ response().document.get());
+ if (SR_SUCCESS != result) {
+ error = HE_STREAM;
+ }
+ }
+
+ return error;
+}
+
+bool HttpClient::PrepareValidate() {
+ ASSERT(CS_READY == cache_state_);
+ // At this point, request() contains the pending request, and response()
+ // contains the cached response headers. Reformat the request to validate
+ // the cached content.
+ HttpValidatorStrength vs_required = HttpRequestValidatorLevel(request());
+ HttpValidatorStrength vs_available = HttpResponseValidatorLevel(response());
+ if (vs_available < vs_required) {
+ return false;
+ }
+ std::string value;
+ if (response().hasHeader(HH_ETAG, &value)) {
+ request().addHeader(HH_IF_NONE_MATCH, value);
+ }
+ if (response().hasHeader(HH_LAST_MODIFIED, &value)) {
+ request().addHeader(HH_IF_MODIFIED_SINCE, value);
+ }
+ response().clear(false);
+ cache_state_ = CS_VALIDATING;
+ return true;
+}
+
+HttpError HttpClient::CompleteValidate() {
+ ASSERT(CS_VALIDATING == cache_state_);
+
+ std::string id = GetCacheID(request());
+
+ // Merge cached headers with new headers
+ HttpError error = ReadCacheHeaders(id, false);
+ if (HE_NONE != error) {
+ // Rewrite merged headers to cache
+ CacheLock lock(cache_, id);
+ error = WriteCacheHeaders(id);
+ }
+ if (HE_NONE != error) {
+ error = ReadCacheBody(id);
+ }
+ return error;
+}
+
+HttpError HttpClient::OnHeaderAvailable(bool ignore_data, bool chunked,
+ size_t data_size) {
+ // If we are ignoring the data, this is an intermediate header.
+ // TODO: don't signal intermediate headers. Instead, do all header-dependent
+ // processing now, and either set up the next request, or fail outright.
+ // TODO: by default, only write response documents with a success code.
+ SignalHeaderAvailable(this, !ignore_data, ignore_data ? 0 : data_size);
+ if (!ignore_data && !chunked && (data_size != SIZE_UNKNOWN)
+ && response().document.get()) {
+ // Attempt to pre-allocate space for the downloaded data.
+ if (!response().document->ReserveSize(data_size)) {
+ return HE_OVERFLOW;
+ }
+ }
+ return HE_NONE;
+}
+
+//
+// HttpBase Implementation
+//
+
+HttpError HttpClient::onHttpHeaderComplete(bool chunked, size_t& data_size) {
+ if (CS_VALIDATING == cache_state_) {
+ if (HC_NOT_MODIFIED == response().scode) {
+ return CompleteValidate();
+ }
+ // Should we remove conditional headers from request?
+ cache_state_ = CS_READY;
+ cache_->DeleteResource(GetCacheID(request()));
+ // Continue processing response as normal
+ }
+
+ ASSERT(!IsCacheActive());
+ if ((request().verb == HV_HEAD) || !HttpCodeHasBody(response().scode)) {
+ // HEAD requests and certain response codes contain no body
+ data_size = 0;
+ }
+ if (ShouldRedirect(NULL)
+ || ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+ && (PROXY_HTTPS == proxy_.type))) {
+ // We're going to issue another request, so ignore the incoming data.
+ base_.set_ignore_data(true);
+ }
+
+ HttpError error = OnHeaderAvailable(base_.ignore_data(), chunked, data_size);
+ if (HE_NONE != error) {
+ return error;
+ }
+
+ if ((NULL != cache_)
+ && !base_.ignore_data()
+ && HttpShouldCache(*transaction_)) {
+ if (BeginCacheFile()) {
+ cache_state_ = CS_WRITING;
+ }
+ }
+ return HE_NONE;
+}
+
+void HttpClient::onHttpComplete(HttpMode mode, HttpError err) {
+ if (((HE_DISCONNECTED == err) || (HE_CONNECT_FAILED == err)
+ || (HE_SOCKET_ERROR == err))
+ && (HC_INTERNAL_SERVER_ERROR == response().scode)
+ && (attempt_ < retries_)) {
+ // If the response code has not changed from the default, then we haven't
+ // received anything meaningful from the server, so we are eligible for a
+ // retry.
+ ++attempt_;
+ if (request().document.get() && !request().document->Rewind()) {
+ // Unable to replay the request document.
+ err = HE_STREAM;
+ } else {
+ release();
+ connect();
+ return;
+ }
+ } else if (err != HE_NONE) {
+ // fall through
+ } else if (mode == HM_CONNECT) {
+ base_.send(&transaction_->request);
+ return;
+ } else if ((mode == HM_SEND) || HttpCodeIsInformational(response().scode)) {
+ // If you're interested in informational headers, catch
+ // SignalHeaderAvailable.
+ base_.recv(&transaction_->response);
+ return;
+ } else {
+ if (!HttpShouldKeepAlive(response())) {
+ LOG(LS_VERBOSE) << "HttpClient: closing socket";
+ base_.stream()->Close();
+ }
+ std::string location;
+ if (ShouldRedirect(&location)) {
+ Url<char> purl(location);
+ set_server(SocketAddress(purl.host(), purl.port()));
+ request().path = purl.full_path();
+ if (response().scode == HC_SEE_OTHER) {
+ request().verb = HV_GET;
+ request().clearHeader(HH_CONTENT_TYPE);
+ request().clearHeader(HH_CONTENT_LENGTH);
+ request().document.reset();
+ } else if (request().document.get() && !request().document->Rewind()) {
+ // Unable to replay the request document.
+ ASSERT(REDIRECT_ALWAYS == redirect_action_);
+ err = HE_STREAM;
+ }
+ if (err == HE_NONE) {
+ ++redirects_;
+ context_.reset();
+ response().clear(false);
+ release();
+ start();
+ return;
+ }
+ } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response().scode)
+ && (PROXY_HTTPS == proxy_.type)) {
+ std::string authorization, auth_method;
+ HttpData::const_iterator begin = response().begin(HH_PROXY_AUTHENTICATE);
+ HttpData::const_iterator end = response().end(HH_PROXY_AUTHENTICATE);
+ for (HttpData::const_iterator it = begin; it != end; ++it) {
+ HttpAuthContext *context = context_.get();
+ HttpAuthResult res = HttpAuthenticate(
+ it->second.data(), it->second.size(),
+ proxy_.address,
+ ToString(request().verb), request().path,
+ proxy_.username, proxy_.password,
+ context, authorization, auth_method);
+ context_.reset(context);
+ if (res == HAR_RESPONSE) {
+ request().setHeader(HH_PROXY_AUTHORIZATION, authorization);
+ if (request().document.get() && !request().document->Rewind()) {
+ err = HE_STREAM;
+ } else {
+ // Explicitly do not reset the HttpAuthContext
+ response().clear(false);
+ // TODO: Reuse socket when authenticating?
+ release();
+ start();
+ return;
+ }
+ } else if (res == HAR_IGNORE) {
+ LOG(INFO) << "Ignoring Proxy-Authenticate: " << auth_method;
+ continue;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+ if (CS_WRITING == cache_state_) {
+ CompleteCacheFile();
+ cache_state_ = CS_READY;
+ } else if (CS_READING == cache_state_) {
+ cache_state_ = CS_READY;
+ }
+ release();
+ SignalHttpClientComplete(this, err);
+}
+
+void HttpClient::onHttpClosed(HttpError err) {
+ // This shouldn't occur, since we return the stream to the pool upon command
+ // completion.
+ ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault
+//////////////////////////////////////////////////////////////////////
+
+HttpClientDefault::HttpClientDefault(SocketFactory* factory,
+ const std::string& agent,
+ HttpTransaction* transaction)
+ : ReuseSocketPool(factory ? factory : Thread::Current()->socketserver()),
+ HttpClient(agent, NULL, transaction) {
+ set_pool(this);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/httpclient.h b/talk/base/httpclient.h
new file mode 100644
index 0000000..b64e55f
--- /dev/null
+++ b/talk/base/httpclient.h
@@ -0,0 +1,213 @@
+/*
+ * 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_HTTPCLIENT_H__
+#define TALK_BASE_HTTPCLIENT_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpbase.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/socketpool.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Client-specific http utilities
+//////////////////////////////////////////////////////////////////////
+
+// Write cache-relevant response headers to output stream. If size is non-null,
+// it contains the length of the output in bytes. output may be null if only
+// the length is desired.
+bool HttpWriteCacheHeaders(const HttpResponseData* response,
+ StreamInterface* output, size_t* size);
+// Read cached headers from a stream, and them merge them into the response
+// object using the specified combine operation.
+bool HttpReadCacheHeaders(StreamInterface* input,
+ HttpResponseData* response,
+ HttpData::HeaderCombine combine);
+
+//////////////////////////////////////////////////////////////////////
+// HttpClient
+// Implements an HTTP 1.1 client.
+//////////////////////////////////////////////////////////////////////
+
+class DiskCache;
+class HttpClient;
+class IPNetPool;
+
+// What to do: Define STRICT_HTTP_ERROR=1 in your makefile. Use HttpError in
+// your code (HttpErrorType should only be used for code that is shared
+// with groups which have not yet migrated).
+#if STRICT_HTTP_ERROR
+typedef HttpError HttpErrorType;
+#else // !STRICT_HTTP_ERROR
+typedef int HttpErrorType;
+#endif // !STRICT_HTTP_ERROR
+
+class HttpClient : private IHttpNotify {
+public:
+ // If HttpRequestData and HttpResponseData objects are provided, they must
+ // be freed by the caller. Otherwise, an internal object is allocated.
+ HttpClient(const std::string& agent, StreamPool* pool,
+ HttpTransaction* transaction = NULL);
+ virtual ~HttpClient();
+
+ void set_pool(StreamPool* pool) { pool_ = pool; }
+
+ void set_agent(const std::string& agent) { agent_ = agent; }
+ const std::string& agent() const { return agent_; }
+
+ void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; }
+ const ProxyInfo& proxy() const { return proxy_; }
+
+ // Request retries occur when the connection closes before the beginning of
+ // an http response is received. In these cases, the http server may have
+ // timed out the keepalive connection before it received our request. Note
+ // that if a request document cannot be rewound, no retry is made. The
+ // default is 1.
+ void set_request_retries(size_t retries) { retries_ = retries; }
+ size_t request_retries() const { return retries_; }
+
+ enum RedirectAction { REDIRECT_DEFAULT, REDIRECT_ALWAYS, REDIRECT_NEVER };
+ void set_redirect_action(RedirectAction action) { redirect_action_ = action; }
+ RedirectAction redirect_action() const { return redirect_action_; }
+ // Deprecated
+ void set_fail_redirect(bool fail_redirect) {
+ redirect_action_ = REDIRECT_NEVER;
+ }
+ bool fail_redirect() const { return (REDIRECT_NEVER == redirect_action_); }
+
+ enum UriForm { URI_DEFAULT, URI_ABSOLUTE, URI_RELATIVE };
+ void set_uri_form(UriForm form) { uri_form_ = form; }
+ UriForm uri_form() const { return uri_form_; }
+
+ void set_cache(DiskCache* cache) { ASSERT(!IsCacheActive()); cache_ = cache; }
+ bool cache_enabled() const { return (NULL != cache_); }
+
+ // reset clears the server, request, and response structures. It will also
+ // abort an active request.
+ void reset();
+
+ void set_server(const SocketAddress& address);
+ const SocketAddress& server() const { return server_; }
+
+ // Note: in order for HttpClient to retry a POST in response to
+ // an authentication challenge, a redirect response, or socket disconnection,
+ // the request document must support 'replaying' by calling Rewind() on it.
+ // In the case where just a subset of a stream should be used as the request
+ // document, the stream may be wrapped with the StreamSegment adapter.
+ HttpTransaction* transaction() { return transaction_; }
+ const HttpTransaction* transaction() const { return transaction_; }
+ HttpRequestData& request() { return transaction_->request; }
+ const HttpRequestData& request() const { return transaction_->request; }
+ HttpResponseData& response() { return transaction_->response; }
+ const HttpResponseData& response() const { return transaction_->response; }
+
+ // convenience methods
+ void prepare_get(const std::string& url);
+ void prepare_post(const std::string& url, const std::string& content_type,
+ StreamInterface* request_doc);
+
+ // Convert HttpClient to a pull-based I/O model.
+ StreamInterface* GetDocumentStream();
+
+ // After you finish setting up your request, call start.
+ void start();
+
+ // Signalled when the header has finished downloading, before the document
+ // content is processed. You may change the response document in response
+ // to this signal. The second parameter indicates whether this is an
+ // intermediate (false) or final (true) header. An intermediate header is
+ // one that generates another request, such as a redirect or authentication
+ // challenge. The third parameter indicates the length of the response
+ // document, or else SIZE_UNKNOWN. Note: Do NOT abort the request in response
+ // to this signal.
+ sigslot::signal3<HttpClient*,bool,size_t> SignalHeaderAvailable;
+ // Signalled when the current request finishes. On success, err is 0.
+ sigslot::signal2<HttpClient*,HttpErrorType> SignalHttpClientComplete;
+
+protected:
+ void connect();
+ void release();
+
+ bool ShouldRedirect(std::string* location) const;
+
+ bool BeginCacheFile();
+ HttpError WriteCacheHeaders(const std::string& id);
+ void CompleteCacheFile();
+
+ bool CheckCache();
+ HttpError ReadCacheHeaders(const std::string& id, bool override);
+ HttpError ReadCacheBody(const std::string& id);
+
+ bool PrepareValidate();
+ HttpError CompleteValidate();
+
+ HttpError OnHeaderAvailable(bool ignore_data, bool chunked, size_t data_size);
+
+ // IHttpNotify Interface
+ virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size);
+ virtual void onHttpComplete(HttpMode mode, HttpError err);
+ virtual void onHttpClosed(HttpError err);
+
+private:
+ enum CacheState { CS_READY, CS_WRITING, CS_READING, CS_VALIDATING };
+ bool IsCacheActive() const { return (cache_state_ > CS_READY); }
+
+ std::string agent_;
+ StreamPool* pool_;
+ HttpBase base_;
+ SocketAddress server_;
+ ProxyInfo proxy_;
+ HttpTransaction* transaction_;
+ bool free_transaction_;
+ size_t retries_, attempt_, redirects_;
+ RedirectAction redirect_action_;
+ UriForm uri_form_;
+ scoped_ptr<HttpAuthContext> context_;
+ DiskCache* cache_;
+ CacheState cache_state_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpClientDefault - Default implementation of HttpClient
+//////////////////////////////////////////////////////////////////////
+
+class HttpClientDefault : public ReuseSocketPool, public HttpClient {
+public:
+ HttpClientDefault(SocketFactory* factory, const std::string& agent,
+ HttpTransaction* transaction = NULL);
+};
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPCLIENT_H__
diff --git a/talk/base/httpcommon-inl.h b/talk/base/httpcommon-inl.h
new file mode 100644
index 0000000..a33a643
--- /dev/null
+++ b/talk/base/httpcommon-inl.h
@@ -0,0 +1,143 @@
+/*
+ * 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_HTTPCOMMON_INL_H__
+#define TALK_BASE_HTTPCOMMON_INL_H__
+
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Url
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_url(const CTYPE* val, size_t len) {
+ if (ascnicmp(val, "http://", 7) == 0) {
+ val += 7; len -= 7;
+ secure_ = false;
+ } else if (ascnicmp(val, "https://", 8) == 0) {
+ val += 8; len -= 8;
+ secure_ = true;
+ } else {
+ clear();
+ return;
+ }
+ const CTYPE* path = strchrn(val, len, static_cast<CTYPE>('/'));
+ if (!path) {
+ path = val + len;
+ }
+ size_t address_length = (path - val);
+ do_set_address(val, address_length);
+ do_set_full_path(path, len - address_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_address(const CTYPE* val, size_t len) {
+ if (const CTYPE* colon = strchrn(val, len, static_cast<CTYPE>(':'))) {
+ host_.assign(val, colon - val);
+ // Note: In every case, we're guaranteed that colon is followed by a null,
+ // or non-numeric character.
+ port_ = static_cast<uint16>(::strtoul(colon + 1, NULL, 10));
+ // TODO: Consider checking for invalid data following port number.
+ } else {
+ host_.assign(val, len);
+ port_ = HttpDefaultPort(secure_);
+ }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_set_full_path(const CTYPE* val, size_t len) {
+ const CTYPE* query = strchrn(val, len, static_cast<CTYPE>('?'));
+ if (!query) {
+ query = val + len;
+ }
+ size_t path_length = (query - val);
+ if (0 == path_length) {
+ // TODO: consider failing in this case.
+ path_.assign(1, static_cast<CTYPE>('/'));
+ } else {
+ ASSERT(val[0] == static_cast<CTYPE>('/'));
+ path_.assign(val, path_length);
+ }
+ query_.assign(query, len - path_length);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_url(string* val) const {
+ CTYPE protocol[9];
+ asccpyn(protocol, ARRAY_SIZE(protocol), secure_ ? "https://" : "http://");
+ val->append(protocol);
+ do_get_address(val);
+ do_get_full_path(val);
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_address(string* val) const {
+ val->append(host_);
+ if (port_ != HttpDefaultPort(secure_)) {
+ CTYPE format[5], port[32];
+ asccpyn(format, ARRAY_SIZE(format), ":%hu");
+ sprintfn(port, ARRAY_SIZE(port), format, port_);
+ val->append(port);
+ }
+}
+
+template<class CTYPE>
+void Url<CTYPE>::do_get_full_path(string* val) const {
+ val->append(path_);
+ val->append(query_);
+}
+
+template<class CTYPE>
+bool Url<CTYPE>::get_attribute(const string& name, string* value) const {
+ if (query_.empty())
+ return false;
+
+ std::string::size_type pos = query_.find(name, 1);
+ if (std::string::npos == pos)
+ return false;
+
+ pos += name.length() + 1;
+ if ((pos > query_.length()) || (static_cast<CTYPE>('=') != query_[pos-1]))
+ return false;
+
+ std::string::size_type end = query_.find(static_cast<CTYPE>('&'), pos);
+ if (std::string::npos == end) {
+ end = query_.length();
+ }
+ value->assign(query_.substr(pos, end - pos));
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPCOMMON_INL_H__
diff --git a/talk/base/httpcommon.cc b/talk/base/httpcommon.cc
new file mode 100644
index 0000000..f7553b2
--- /dev/null
+++ b/talk/base/httpcommon.cc
@@ -0,0 +1,1054 @@
+/*
+ * 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 <time.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/httpcommon-inl.h"
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringdigest.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+#ifdef WIN32
+extern const ConstantLabel SECURITY_ERRORS[];
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// Enum - TODO: expose globally later?
+//////////////////////////////////////////////////////////////////////
+
+bool find_string(size_t& index, const std::string& needle,
+ const char* const haystack[], size_t max_index) {
+ for (index=0; index<max_index; ++index) {
+ if (_stricmp(needle.c_str(), haystack[index]) == 0) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template<class E>
+struct Enum {
+ static const char** Names;
+ static size_t Size;
+
+ static inline const char* Name(E val) { return Names[val]; }
+ static inline bool Parse(E& val, const std::string& name) {
+ size_t index;
+ if (!find_string(index, name, Names, Size))
+ return false;
+ val = static_cast<E>(index);
+ return true;
+ }
+
+ E val;
+
+ inline operator E&() { return val; }
+ inline Enum& operator=(E rhs) { val = rhs; return *this; }
+
+ inline const char* name() const { return Name(val); }
+ inline bool assign(const std::string& name) { return Parse(val, name); }
+ inline Enum& operator=(const std::string& rhs) { assign(rhs); return *this; }
+};
+
+#define ENUM(e,n) \
+ template<> const char** Enum<e>::Names = n; \
+ template<> size_t Enum<e>::Size = sizeof(n)/sizeof(n[0])
+
+//////////////////////////////////////////////////////////////////////
+// HttpCommon
+//////////////////////////////////////////////////////////////////////
+
+static const char* kHttpVersions[HVER_LAST+1] = {
+ "1.0", "1.1", "Unknown"
+};
+ENUM(HttpVersion, kHttpVersions);
+
+static const char* kHttpVerbs[HV_LAST+1] = {
+ "GET", "POST", "PUT", "DELETE", "CONNECT", "HEAD"
+};
+ENUM(HttpVerb, kHttpVerbs);
+
+static const char* kHttpHeaders[HH_LAST+1] = {
+ "Age",
+ "Cache-Control",
+ "Connection",
+ "Content-Disposition",
+ "Content-Length",
+ "Content-Range",
+ "Content-Type",
+ "Cookie",
+ "Date",
+ "ETag",
+ "Expires",
+ "Host",
+ "If-Modified-Since",
+ "If-None-Match",
+ "Keep-Alive",
+ "Last-Modified",
+ "Location",
+ "Proxy-Authenticate",
+ "Proxy-Authorization",
+ "Proxy-Connection",
+ "Range",
+ "Set-Cookie",
+ "TE",
+ "Trailers",
+ "Transfer-Encoding",
+ "Upgrade",
+ "User-Agent",
+ "WWW-Authenticate",
+};
+ENUM(HttpHeader, kHttpHeaders);
+
+const char* ToString(HttpVersion version) {
+ return Enum<HttpVersion>::Name(version);
+}
+
+bool FromString(HttpVersion& version, const std::string& str) {
+ return Enum<HttpVersion>::Parse(version, str);
+}
+
+const char* ToString(HttpVerb verb) {
+ return Enum<HttpVerb>::Name(verb);
+}
+
+bool FromString(HttpVerb& verb, const std::string& str) {
+ return Enum<HttpVerb>::Parse(verb, str);
+}
+
+const char* ToString(HttpHeader header) {
+ return Enum<HttpHeader>::Name(header);
+}
+
+bool FromString(HttpHeader& header, const std::string& str) {
+ return Enum<HttpHeader>::Parse(header, str);
+}
+
+bool HttpCodeHasBody(uint32 code) {
+ return !HttpCodeIsInformational(code)
+ && (code != HC_NO_CONTENT) && (code != HC_NOT_MODIFIED);
+}
+
+bool HttpCodeIsCacheable(uint32 code) {
+ switch (code) {
+ case HC_OK:
+ case HC_NON_AUTHORITATIVE:
+ case HC_PARTIAL_CONTENT:
+ case HC_MULTIPLE_CHOICES:
+ case HC_MOVED_PERMANENTLY:
+ case HC_GONE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool HttpHeaderIsEndToEnd(HttpHeader header) {
+ switch (header) {
+ case HH_CONNECTION:
+ case HH_KEEP_ALIVE:
+ case HH_PROXY_AUTHENTICATE:
+ case HH_PROXY_AUTHORIZATION:
+ case HH_PROXY_CONNECTION: // Note part of RFC... this is non-standard header
+ case HH_TE:
+ case HH_TRAILERS:
+ case HH_TRANSFER_ENCODING:
+ case HH_UPGRADE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool HttpHeaderIsCollapsible(HttpHeader header) {
+ switch (header) {
+ case HH_SET_COOKIE:
+ case HH_PROXY_AUTHENTICATE:
+ case HH_WWW_AUTHENTICATE:
+ return false;
+ default:
+ return true;
+ }
+}
+
+bool HttpShouldKeepAlive(const HttpData& data) {
+ std::string connection;
+ if ((data.hasHeader(HH_PROXY_CONNECTION, &connection)
+ || data.hasHeader(HH_CONNECTION, &connection))) {
+ return (_stricmp(connection.c_str(), "Keep-Alive") == 0);
+ }
+ return (data.version >= HVER_1_1);
+}
+
+namespace {
+
+inline bool IsEndOfAttributeName(size_t pos, size_t len, const char * data) {
+ if (pos >= len)
+ return true;
+ if (isspace(static_cast<unsigned char>(data[pos])))
+ return true;
+ // The reason for this complexity is that some attributes may contain trailing
+ // equal signs (like base64 tokens in Negotiate auth headers)
+ if ((pos+1 < len) && (data[pos] == '=') &&
+ !isspace(static_cast<unsigned char>(data[pos+1])) &&
+ (data[pos+1] != '=')) {
+ return true;
+ }
+ return false;
+}
+
+// TODO: unittest for EscapeAttribute and HttpComposeAttributes.
+
+std::string EscapeAttribute(const std::string& attribute) {
+ const size_t kMaxLength = attribute.length() * 2 + 1;
+ char* buffer = STACK_ARRAY(char, kMaxLength);
+ size_t len = escape(buffer, kMaxLength, attribute.data(), attribute.length(),
+ "\"", '\\');
+ return std::string(buffer, len);
+}
+
+} // anonymous namespace
+
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+ std::string* composed) {
+ std::stringstream ss;
+ for (size_t i=0; i<attributes.size(); ++i) {
+ if (i > 0) {
+ ss << separator << " ";
+ }
+ ss << attributes[i].first;
+ if (!attributes[i].second.empty()) {
+ ss << "=\"" << EscapeAttribute(attributes[i].second) << "\"";
+ }
+ }
+ *composed = ss.str();
+}
+
+void HttpParseAttributes(const char * data, size_t len,
+ HttpAttributeList& attributes) {
+ size_t pos = 0;
+ while (true) {
+ // Skip leading whitespace
+ while ((pos < len) && isspace(static_cast<unsigned char>(data[pos]))) {
+ ++pos;
+ }
+
+ // End of attributes?
+ if (pos >= len)
+ return;
+
+ // Find end of attribute name
+ size_t start = pos;
+ while (!IsEndOfAttributeName(pos, len, data)) {
+ ++pos;
+ }
+
+ HttpAttribute attribute;
+ attribute.first.assign(data + start, data + pos);
+
+ // Attribute has value?
+ if ((pos < len) && (data[pos] == '=')) {
+ ++pos; // Skip '='
+ // Check if quoted value
+ if ((pos < len) && (data[pos] == '"')) {
+ while (++pos < len) {
+ if (data[pos] == '"') {
+ ++pos;
+ break;
+ }
+ if ((data[pos] == '\\') && (pos + 1 < len))
+ ++pos;
+ attribute.second.append(1, data[pos]);
+ }
+ } else {
+ while ((pos < len) &&
+ !isspace(static_cast<unsigned char>(data[pos])) &&
+ (data[pos] != ',')) {
+ attribute.second.append(1, data[pos++]);
+ }
+ }
+ }
+
+ attributes.push_back(attribute);
+ if ((pos < len) && (data[pos] == ',')) ++pos; // Skip ','
+ }
+}
+
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+ const std::string& name,
+ std::string* value) {
+ for (HttpAttributeList::const_iterator it = attributes.begin();
+ it != attributes.end(); ++it) {
+ if (it->first == name) {
+ if (value) {
+ *value = it->second;
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+ size_t index,
+ std::string* name,
+ std::string* value) {
+ if (index >= attributes.size())
+ return false;
+
+ if (name)
+ *name = attributes[index].first;
+ if (value)
+ *value = attributes[index].second;
+ return true;
+}
+
+bool HttpDateToSeconds(const std::string& date, unsigned long* seconds) {
+ const char* const kTimeZones[] = {
+ "UT", "GMT", "EST", "EDT", "CST", "CDT", "MST", "MDT", "PST", "PDT",
+ "A", "B", "C", "D", "E", "F", "G", "H", "I", "K", "L", "M",
+ "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"
+ };
+ const int kTimeZoneOffsets[] = {
+ 0, 0, -5, -4, -6, -5, -7, -6, -8, -7,
+ -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12,
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
+ };
+
+ ASSERT(NULL != seconds);
+ struct tm tval;
+ memset(&tval, 0, sizeof(tval));
+ char month[4], zone[6];
+ memset(month, 0, sizeof(month));
+ memset(zone, 0, sizeof(zone));
+
+ if (7 != sscanf(date.c_str(), "%*3s, %d %3s %d %d:%d:%d %5c",
+ &tval.tm_mday, month, &tval.tm_year,
+ &tval.tm_hour, &tval.tm_min, &tval.tm_sec, zone)) {
+ return false;
+ }
+ switch (toupper(month[2])) {
+ case 'N': tval.tm_mon = (month[1] == 'A') ? 0 : 5; break;
+ case 'B': tval.tm_mon = 1; break;
+ case 'R': tval.tm_mon = (month[0] == 'M') ? 2 : 3; break;
+ case 'Y': tval.tm_mon = 4; break;
+ case 'L': tval.tm_mon = 6; break;
+ case 'G': tval.tm_mon = 7; break;
+ case 'P': tval.tm_mon = 8; break;
+ case 'T': tval.tm_mon = 9; break;
+ case 'V': tval.tm_mon = 10; break;
+ case 'C': tval.tm_mon = 11; break;
+ }
+ tval.tm_year -= 1900;
+ unsigned long gmt, non_gmt = mktime(&tval);
+ if ((zone[0] == '+') || (zone[0] == '-')) {
+ if (!isdigit(zone[1]) || !isdigit(zone[2])
+ || !isdigit(zone[3]) || !isdigit(zone[4])) {
+ return false;
+ }
+ int hours = (zone[1] - '0') * 10 + (zone[2] - '0');
+ int minutes = (zone[3] - '0') * 10 + (zone[4] - '0');
+ int offset = (hours * 60 + minutes) * 60;
+ gmt = non_gmt + (zone[0] == '+') ? offset : -offset;
+ } else {
+ size_t zindex;
+ if (!find_string(zindex, zone, kTimeZones, ARRAY_SIZE(kTimeZones))) {
+ return false;
+ }
+ gmt = non_gmt + kTimeZoneOffsets[zindex] * 60 * 60;
+ }
+ // TODO: Android should support timezone, see b/2441195
+#if defined(OSX) || defined(ANDROID) || defined(BSD)
+ tm *tm_for_timezone = localtime((time_t *)&gmt);
+ *seconds = gmt + tm_for_timezone->tm_gmtoff;
+#else
+ *seconds = gmt - timezone;
+#endif
+ return true;
+}
+
+std::string HttpAddress(const SocketAddress& address, bool secure) {
+ return (address.port() == HttpDefaultPort(secure))
+ ? address.hostname() : address.ToString();
+}
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+void
+HttpData::clear(bool release_document) {
+ // Clear headers first, since releasing a document may have far-reaching
+ // effects.
+ headers_.clear();
+ if (release_document) {
+ document.reset();
+ }
+}
+
+void
+HttpData::copy(const HttpData& src) {
+ headers_ = src.headers_;
+}
+
+void
+HttpData::changeHeader(const std::string& name, const std::string& value,
+ HeaderCombine combine) {
+ if (combine == HC_AUTO) {
+ HttpHeader header;
+ // Unrecognized headers are collapsible
+ combine = !FromString(header, name) || HttpHeaderIsCollapsible(header)
+ ? HC_YES : HC_NO;
+ } else if (combine == HC_REPLACE) {
+ headers_.erase(name);
+ combine = HC_NO;
+ }
+ // At this point, combine is one of (YES, NO, NEW)
+ if (combine != HC_NO) {
+ HeaderMap::iterator it = headers_.find(name);
+ if (it != headers_.end()) {
+ if (combine == HC_YES) {
+ it->second.append(",");
+ it->second.append(value);
+ }
+ return;
+ }
+ }
+ headers_.insert(HeaderMap::value_type(name, value));
+}
+
+size_t HttpData::clearHeader(const std::string& name) {
+ return headers_.erase(name);
+}
+
+HttpData::iterator HttpData::clearHeader(iterator header) {
+ iterator deprecated = header++;
+ headers_.erase(deprecated);
+ return header;
+}
+
+bool
+HttpData::hasHeader(const std::string& name, std::string* value) const {
+ HeaderMap::const_iterator it = headers_.find(name);
+ if (it == headers_.end()) {
+ return false;
+ } else if (value) {
+ *value = it->second;
+ }
+ return true;
+}
+
+void HttpData::setContent(const std::string& content_type,
+ StreamInterface* document) {
+ setHeader(HH_CONTENT_TYPE, content_type);
+ setDocumentAndLength(document);
+}
+
+void HttpData::setDocumentAndLength(StreamInterface* document) {
+ // TODO: Consider calling Rewind() here?
+ ASSERT(!hasHeader(HH_CONTENT_LENGTH, NULL));
+ ASSERT(!hasHeader(HH_TRANSFER_ENCODING, NULL));
+ ASSERT(document != NULL);
+ this->document.reset(document);
+ size_t content_length = 0;
+ if (this->document->GetAvailable(&content_length)) {
+ char buffer[32];
+ sprintfn(buffer, sizeof(buffer), "%d", content_length);
+ setHeader(HH_CONTENT_LENGTH, buffer);
+ } else {
+ setHeader(HH_TRANSFER_ENCODING, "chunked");
+ }
+}
+
+//
+// HttpRequestData
+//
+
+void
+HttpRequestData::clear(bool release_document) {
+ verb = HV_GET;
+ path.clear();
+ HttpData::clear(release_document);
+}
+
+void
+HttpRequestData::copy(const HttpRequestData& src) {
+ verb = src.verb;
+ path = src.path;
+ HttpData::copy(src);
+}
+
+size_t
+HttpRequestData::formatLeader(char* buffer, size_t size) const {
+ ASSERT(path.find(' ') == std::string::npos);
+ return sprintfn(buffer, size, "%s %.*s HTTP/%s", ToString(verb), path.size(),
+ path.data(), ToString(version));
+}
+
+HttpError
+HttpRequestData::parseLeader(const char* line, size_t len) {
+ UNUSED(len);
+ unsigned int vmajor, vminor;
+ int vend, dstart, dend;
+ if ((sscanf(line, "%*s%n %n%*s%n HTTP/%u.%u", &vend, &dstart, &dend,
+ &vmajor, &vminor) != 2)
+ || (vmajor != 1)) {
+ return HE_PROTOCOL;
+ }
+ if (vminor == 0) {
+ version = HVER_1_0;
+ } else if (vminor == 1) {
+ version = HVER_1_1;
+ } else {
+ return HE_PROTOCOL;
+ }
+ std::string sverb(line, vend);
+ if (!FromString(verb, sverb.c_str())) {
+ return HE_PROTOCOL; // !?! HC_METHOD_NOT_SUPPORTED?
+ }
+ path.assign(line + dstart, line + dend);
+ return HE_NONE;
+}
+
+bool HttpRequestData::getAbsoluteUri(std::string* uri) const {
+ if (HV_CONNECT == verb)
+ return false;
+ Url<char> url(path);
+ if (url.valid()) {
+ uri->assign(path);
+ return true;
+ }
+ std::string host;
+ if (!hasHeader(HH_HOST, &host))
+ return false;
+ url.set_address(host);
+ url.set_full_path(path);
+ uri->assign(url.url());
+ return url.valid();
+}
+
+bool HttpRequestData::getRelativeUri(std::string* host,
+ std::string* path) const
+{
+ if (HV_CONNECT == verb)
+ return false;
+ Url<char> url(this->path);
+ if (url.valid()) {
+ host->assign(url.address());
+ path->assign(url.full_path());
+ return true;
+ }
+ if (!hasHeader(HH_HOST, host))
+ return false;
+ path->assign(this->path);
+ return true;
+}
+
+//
+// HttpResponseData
+//
+
+void
+HttpResponseData::clear(bool release_document) {
+ scode = HC_INTERNAL_SERVER_ERROR;
+ message.clear();
+ HttpData::clear(release_document);
+}
+
+void
+HttpResponseData::copy(const HttpResponseData& src) {
+ scode = src.scode;
+ message = src.message;
+ HttpData::copy(src);
+}
+
+void
+HttpResponseData::set_success(uint32 scode) {
+ this->scode = scode;
+ message.clear();
+ setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_success(const std::string& content_type,
+ StreamInterface* document,
+ uint32 scode) {
+ this->scode = scode;
+ message.erase(message.begin(), message.end());
+ setContent(content_type, document);
+}
+
+void
+HttpResponseData::set_redirect(const std::string& location, uint32 scode) {
+ this->scode = scode;
+ message.clear();
+ setHeader(HH_LOCATION, location);
+ setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+void
+HttpResponseData::set_error(uint32 scode) {
+ this->scode = scode;
+ message.clear();
+ setHeader(HH_CONTENT_LENGTH, "0", false);
+}
+
+size_t
+HttpResponseData::formatLeader(char* buffer, size_t size) const {
+ size_t len = sprintfn(buffer, size, "HTTP/%s %lu", ToString(version), scode);
+ if (!message.empty()) {
+ len += sprintfn(buffer + len, size - len, " %.*s",
+ message.size(), message.data());
+ }
+ return len;
+}
+
+HttpError
+HttpResponseData::parseLeader(const char* line, size_t len) {
+ size_t pos = 0;
+ unsigned int vmajor, vminor, temp_scode;
+ int temp_pos;
+ if (sscanf(line, "HTTP %u%n",
+ &temp_scode, &temp_pos) == 1) {
+ // This server's response has no version. :( NOTE: This happens for every
+ // response to requests made from Chrome plugins, regardless of the server's
+ // behaviour.
+ LOG(LS_VERBOSE) << "HTTP version missing from response";
+ version = HVER_UNKNOWN;
+ } else if ((sscanf(line, "HTTP/%u.%u %u%n",
+ &vmajor, &vminor, &temp_scode, &temp_pos) == 3)
+ && (vmajor == 1)) {
+ // This server's response does have a version.
+ if (vminor == 0) {
+ version = HVER_1_0;
+ } else if (vminor == 1) {
+ version = HVER_1_1;
+ } else {
+ return HE_PROTOCOL;
+ }
+ } else {
+ return HE_PROTOCOL;
+ }
+ scode = temp_scode;
+ pos = static_cast<size_t>(temp_pos);
+ while ((pos < len) && isspace(static_cast<unsigned char>(line[pos]))) ++pos;
+ message.assign(line + pos, len - pos);
+ return HE_NONE;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+#define TEST_DIGEST 0
+#if TEST_DIGEST
+/*
+const char * const DIGEST_CHALLENGE =
+ "Digest realm=\"testrealm@host.com\","
+ " qop=\"auth,auth-int\","
+ " nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ " opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
+const char * const DIGEST_METHOD = "GET";
+const char * const DIGEST_URI =
+ "/dir/index.html";;
+const char * const DIGEST_CNONCE =
+ "0a4f113b";
+const char * const DIGEST_RESPONSE =
+ "6629fae49393a05397450978507c4ef1";
+//user_ = "Mufasa";
+//pass_ = "Circle Of Life";
+*/
+const char * const DIGEST_CHALLENGE =
+ "Digest realm=\"Squid proxy-caching web server\","
+ " nonce=\"Nny4QuC5PwiSDixJ\","
+ " qop=\"auth\","
+ " stale=false";
+const char * const DIGEST_URI =
+ "/";
+const char * const DIGEST_CNONCE =
+ "6501d58e9a21cee1e7b5fec894ded024";
+const char * const DIGEST_RESPONSE =
+ "edffcb0829e755838b073a4a42de06bc";
+#endif
+
+std::string quote(const std::string& str) {
+ std::string result;
+ result.push_back('"');
+ for (size_t i=0; i<str.size(); ++i) {
+ if ((str[i] == '"') || (str[i] == '\\'))
+ result.push_back('\\');
+ result.push_back(str[i]);
+ }
+ result.push_back('"');
+ return result;
+}
+
+#ifdef WIN32
+struct NegotiateAuthContext : public HttpAuthContext {
+ CredHandle cred;
+ CtxtHandle ctx;
+ size_t steps;
+ bool specified_credentials;
+
+ NegotiateAuthContext(const std::string& auth, CredHandle c1, CtxtHandle c2)
+ : HttpAuthContext(auth), cred(c1), ctx(c2), steps(0),
+ specified_credentials(false)
+ { }
+
+ virtual ~NegotiateAuthContext() {
+ DeleteSecurityContext(&ctx);
+ FreeCredentialsHandle(&cred);
+ }
+};
+#endif // WIN32
+
+HttpAuthResult HttpAuthenticate(
+ const char * challenge, size_t len,
+ const SocketAddress& server,
+ const std::string& method, const std::string& uri,
+ const std::string& username, const CryptString& password,
+ HttpAuthContext *& context, std::string& response, std::string& auth_method)
+{
+#if TEST_DIGEST
+ challenge = DIGEST_CHALLENGE;
+ len = strlen(challenge);
+#endif
+
+ HttpAttributeList args;
+ HttpParseAttributes(challenge, len, args);
+ HttpHasNthAttribute(args, 0, &auth_method, NULL);
+
+ if (context && (context->auth_method != auth_method))
+ return HAR_IGNORE;
+
+ // BASIC
+ if (_stricmp(auth_method.c_str(), "basic") == 0) {
+ if (context)
+ return HAR_CREDENTIALS; // Bad credentials
+ if (username.empty())
+ return HAR_CREDENTIALS; // Missing credentials
+
+ context = new HttpAuthContext(auth_method);
+
+ // TODO: convert sensitive to a secure buffer that gets securely deleted
+ //std::string decoded = username + ":" + password;
+ size_t len = username.size() + password.GetLength() + 2;
+ char * sensitive = new char[len];
+ size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+ pos += strcpyn(sensitive + pos, len - pos, ":");
+ password.CopyTo(sensitive + pos, true);
+
+ response = auth_method;
+ response.append(" ");
+ // TODO: create a sensitive-source version of Base64::encode
+ response.append(Base64::Encode(sensitive));
+ memset(sensitive, 0, len);
+ delete [] sensitive;
+ return HAR_RESPONSE;
+ }
+
+ // DIGEST
+ if (_stricmp(auth_method.c_str(), "digest") == 0) {
+ if (context)
+ return HAR_CREDENTIALS; // Bad credentials
+ if (username.empty())
+ return HAR_CREDENTIALS; // Missing credentials
+
+ context = new HttpAuthContext(auth_method);
+
+ std::string cnonce, ncount;
+#if TEST_DIGEST
+ method = DIGEST_METHOD;
+ uri = DIGEST_URI;
+ cnonce = DIGEST_CNONCE;
+#else
+ char buffer[256];
+ sprintf(buffer, "%d", static_cast<int>(time(0)));
+ cnonce = MD5(buffer);
+#endif
+ ncount = "00000001";
+
+ std::string realm, nonce, qop, opaque;
+ HttpHasAttribute(args, "realm", &realm);
+ HttpHasAttribute(args, "nonce", &nonce);
+ bool has_qop = HttpHasAttribute(args, "qop", &qop);
+ bool has_opaque = HttpHasAttribute(args, "opaque", &opaque);
+
+ // TODO: convert sensitive to be secure buffer
+ //std::string A1 = username + ":" + realm + ":" + password;
+ size_t len = username.size() + realm.size() + password.GetLength() + 3;
+ char * sensitive = new char[len]; // A1
+ size_t pos = strcpyn(sensitive, len, username.data(), username.size());
+ pos += strcpyn(sensitive + pos, len - pos, ":");
+ pos += strcpyn(sensitive + pos, len - pos, realm.c_str());
+ pos += strcpyn(sensitive + pos, len - pos, ":");
+ password.CopyTo(sensitive + pos, true);
+
+ std::string A2 = method + ":" + uri;
+ std::string middle;
+ if (has_qop) {
+ qop = "auth";
+ middle = nonce + ":" + ncount + ":" + cnonce + ":" + qop;
+ } else {
+ middle = nonce;
+ }
+ std::string HA1 = MD5(sensitive);
+ memset(sensitive, 0, len);
+ delete [] sensitive;
+ std::string HA2 = MD5(A2);
+ std::string dig_response = MD5(HA1 + ":" + middle + ":" + HA2);
+
+#if TEST_DIGEST
+ ASSERT(strcmp(dig_response.c_str(), DIGEST_RESPONSE) == 0);
+#endif
+
+ std::stringstream ss;
+ ss << auth_method;
+ ss << " username=" << quote(username);
+ ss << ", realm=" << quote(realm);
+ ss << ", nonce=" << quote(nonce);
+ ss << ", uri=" << quote(uri);
+ if (has_qop) {
+ ss << ", qop=" << qop;
+ ss << ", nc=" << ncount;
+ ss << ", cnonce=" << quote(cnonce);
+ }
+ ss << ", response=\"" << dig_response << "\"";
+ if (has_opaque) {
+ ss << ", opaque=" << quote(opaque);
+ }
+ response = ss.str();
+ return HAR_RESPONSE;
+ }
+
+#ifdef WIN32
+#if 1
+ bool want_negotiate = (_stricmp(auth_method.c_str(), "negotiate") == 0);
+ bool want_ntlm = (_stricmp(auth_method.c_str(), "ntlm") == 0);
+ // SPNEGO & NTLM
+ if (want_negotiate || want_ntlm) {
+ const size_t MAX_MESSAGE = 12000, MAX_SPN = 256;
+ char out_buf[MAX_MESSAGE], spn[MAX_SPN];
+
+#if 0 // Requires funky windows versions
+ DWORD len = MAX_SPN;
+ if (DsMakeSpn("HTTP", server.IPAsString().c_str(), NULL, server.port(),
+ 0, &len, spn) != ERROR_SUCCESS) {
+ LOG_F(WARNING) << "(Negotiate) - DsMakeSpn failed";
+ return HAR_IGNORE;
+ }
+#else
+ sprintfn(spn, MAX_SPN, "HTTP/%s", server.ToString().c_str());
+#endif
+
+ SecBuffer out_sec;
+ out_sec.pvBuffer = out_buf;
+ out_sec.cbBuffer = sizeof(out_buf);
+ out_sec.BufferType = SECBUFFER_TOKEN;
+
+ SecBufferDesc out_buf_desc;
+ out_buf_desc.ulVersion = 0;
+ out_buf_desc.cBuffers = 1;
+ out_buf_desc.pBuffers = &out_sec;
+
+ const ULONG NEG_FLAGS_DEFAULT =
+ //ISC_REQ_ALLOCATE_MEMORY
+ ISC_REQ_CONFIDENTIALITY
+ //| ISC_REQ_EXTENDED_ERROR
+ //| ISC_REQ_INTEGRITY
+ | ISC_REQ_REPLAY_DETECT
+ | ISC_REQ_SEQUENCE_DETECT
+ //| ISC_REQ_STREAM
+ //| ISC_REQ_USE_SUPPLIED_CREDS
+ ;
+
+ ::TimeStamp lifetime;
+ SECURITY_STATUS ret = S_OK;
+ ULONG ret_flags = 0, flags = NEG_FLAGS_DEFAULT;
+
+ bool specify_credentials = !username.empty();
+ size_t steps = 0;
+
+ //uint32 now = Time();
+
+ NegotiateAuthContext * neg = static_cast<NegotiateAuthContext *>(context);
+ if (neg) {
+ const size_t max_steps = 10;
+ if (++neg->steps >= max_steps) {
+ LOG(WARNING) << "AsyncHttpsProxySocket::Authenticate(Negotiate) too many retries";
+ return HAR_ERROR;
+ }
+ steps = neg->steps;
+
+ std::string challenge, decoded_challenge;
+ if (HttpHasNthAttribute(args, 1, &challenge, NULL)
+ && Base64::Decode(challenge, Base64::DO_STRICT,
+ &decoded_challenge, NULL)) {
+ SecBuffer in_sec;
+ in_sec.pvBuffer = const_cast<char *>(decoded_challenge.data());
+ in_sec.cbBuffer = static_cast<unsigned long>(decoded_challenge.size());
+ in_sec.BufferType = SECBUFFER_TOKEN;
+
+ SecBufferDesc in_buf_desc;
+ in_buf_desc.ulVersion = 0;
+ in_buf_desc.cBuffers = 1;
+ in_buf_desc.pBuffers = &in_sec;
+
+ ret = InitializeSecurityContextA(&neg->cred, &neg->ctx, spn, flags, 0, SECURITY_NATIVE_DREP, &in_buf_desc, 0, &neg->ctx, &out_buf_desc, &ret_flags, &lifetime);
+ //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+ if (FAILED(ret)) {
+ LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+ << ErrorName(ret, SECURITY_ERRORS);
+ return HAR_ERROR;
+ }
+ } else if (neg->specified_credentials) {
+ // Try again with default credentials
+ specify_credentials = false;
+ delete context;
+ context = neg = 0;
+ } else {
+ return HAR_CREDENTIALS;
+ }
+ }
+
+ if (!neg) {
+ unsigned char userbuf[256], passbuf[256], domainbuf[16];
+ SEC_WINNT_AUTH_IDENTITY_A auth_id, * pauth_id = 0;
+ if (specify_credentials) {
+ memset(&auth_id, 0, sizeof(auth_id));
+ size_t len = password.GetLength()+1;
+ char * sensitive = new char[len];
+ password.CopyTo(sensitive, true);
+ std::string::size_type pos = username.find('\\');
+ if (pos == std::string::npos) {
+ auth_id.UserLength = static_cast<unsigned long>(
+ _min(sizeof(userbuf) - 1, username.size()));
+ memcpy(userbuf, username.c_str(), auth_id.UserLength);
+ userbuf[auth_id.UserLength] = 0;
+ auth_id.DomainLength = 0;
+ domainbuf[auth_id.DomainLength] = 0;
+ auth_id.PasswordLength = static_cast<unsigned long>(
+ _min(sizeof(passbuf) - 1, password.GetLength()));
+ memcpy(passbuf, sensitive, auth_id.PasswordLength);
+ passbuf[auth_id.PasswordLength] = 0;
+ } else {
+ auth_id.UserLength = static_cast<unsigned long>(
+ _min(sizeof(userbuf) - 1, username.size() - pos - 1));
+ memcpy(userbuf, username.c_str() + pos + 1, auth_id.UserLength);
+ userbuf[auth_id.UserLength] = 0;
+ auth_id.DomainLength = static_cast<unsigned long>(
+ _min(sizeof(domainbuf) - 1, pos));
+ memcpy(domainbuf, username.c_str(), auth_id.DomainLength);
+ domainbuf[auth_id.DomainLength] = 0;
+ auth_id.PasswordLength = static_cast<unsigned long>(
+ _min(sizeof(passbuf) - 1, password.GetLength()));
+ memcpy(passbuf, sensitive, auth_id.PasswordLength);
+ passbuf[auth_id.PasswordLength] = 0;
+ }
+ memset(sensitive, 0, len);
+ delete [] sensitive;
+ auth_id.User = userbuf;
+ auth_id.Domain = domainbuf;
+ auth_id.Password = passbuf;
+ auth_id.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
+ pauth_id = &auth_id;
+ LOG(LS_VERBOSE) << "Negotiate protocol: Using specified credentials";
+ } else {
+ LOG(LS_VERBOSE) << "Negotiate protocol: Using default credentials";
+ }
+
+ CredHandle cred;
+ ret = AcquireCredentialsHandleA(0, want_negotiate ? NEGOSSP_NAME_A : NTLMSP_NAME_A, SECPKG_CRED_OUTBOUND, 0, pauth_id, 0, 0, &cred, &lifetime);
+ //LOG(INFO) << "$$$ AcquireCredentialsHandle @ " << TimeSince(now);
+ if (ret != SEC_E_OK) {
+ LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+ << ErrorName(ret, SECURITY_ERRORS);
+ return HAR_IGNORE;
+ }
+
+ //CSecBufferBundle<5, CSecBufferBase::FreeSSPI> sb_out;
+
+ CtxtHandle ctx;
+ ret = InitializeSecurityContextA(&cred, 0, spn, flags, 0, SECURITY_NATIVE_DREP, 0, 0, &ctx, &out_buf_desc, &ret_flags, &lifetime);
+ //LOG(INFO) << "$$$ InitializeSecurityContext @ " << TimeSince(now);
+ if (FAILED(ret)) {
+ LOG(LS_ERROR) << "InitializeSecurityContext returned: "
+ << ErrorName(ret, SECURITY_ERRORS);
+ FreeCredentialsHandle(&cred);
+ return HAR_IGNORE;
+ }
+
+ ASSERT(!context);
+ context = neg = new NegotiateAuthContext(auth_method, cred, ctx);
+ neg->specified_credentials = specify_credentials;
+ neg->steps = steps;
+ }
+
+ if ((ret == SEC_I_COMPLETE_NEEDED) || (ret == SEC_I_COMPLETE_AND_CONTINUE)) {
+ ret = CompleteAuthToken(&neg->ctx, &out_buf_desc);
+ //LOG(INFO) << "$$$ CompleteAuthToken @ " << TimeSince(now);
+ LOG(LS_VERBOSE) << "CompleteAuthToken returned: "
+ << ErrorName(ret, SECURITY_ERRORS);
+ if (FAILED(ret)) {
+ return HAR_ERROR;
+ }
+ }
+
+ //LOG(INFO) << "$$$ NEGOTIATE took " << TimeSince(now) << "ms";
+
+ std::string decoded(out_buf, out_buf + out_sec.cbBuffer);
+ response = auth_method;
+ response.append(" ");
+ response.append(Base64::Encode(decoded));
+ return HAR_RESPONSE;
+ }
+#endif
+#endif // WIN32
+
+ return HAR_IGNORE;
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/httpcommon.h b/talk/base/httpcommon.h
new file mode 100644
index 0000000..7e0b9cf
--- /dev/null
+++ b/talk/base/httpcommon.h
@@ -0,0 +1,463 @@
+/*
+ * 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_HTTPCOMMON_H__
+#define TALK_BASE_HTTPCOMMON_H__
+
+#include <map>
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+class CryptString;
+class SocketAddress;
+
+//////////////////////////////////////////////////////////////////////
+// Constants
+//////////////////////////////////////////////////////////////////////
+
+enum HttpCode {
+ HC_OK = 200,
+ HC_NON_AUTHORITATIVE = 203,
+ HC_NO_CONTENT = 204,
+ HC_PARTIAL_CONTENT = 206,
+
+ HC_MULTIPLE_CHOICES = 300,
+ HC_MOVED_PERMANENTLY = 301,
+ HC_FOUND = 302,
+ HC_SEE_OTHER = 303,
+ HC_NOT_MODIFIED = 304,
+ HC_MOVED_TEMPORARILY = 307,
+
+ HC_BAD_REQUEST = 400,
+ HC_UNAUTHORIZED = 401,
+ HC_FORBIDDEN = 403,
+ HC_NOT_FOUND = 404,
+ HC_PROXY_AUTHENTICATION_REQUIRED = 407,
+ HC_GONE = 410,
+
+ HC_INTERNAL_SERVER_ERROR = 500,
+ HC_NOT_IMPLEMENTED = 501,
+ HC_SERVICE_UNAVAILABLE = 503,
+};
+
+enum HttpVersion {
+ HVER_1_0, HVER_1_1, HVER_UNKNOWN,
+ HVER_LAST = HVER_UNKNOWN
+};
+
+enum HttpVerb {
+ HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD,
+ HV_LAST = HV_HEAD
+};
+
+enum HttpError {
+ HE_NONE,
+ HE_PROTOCOL, // Received non-valid HTTP data
+ HE_DISCONNECTED, // Connection closed unexpectedly
+ HE_OVERFLOW, // Received too much data for internal buffers
+ HE_CONNECT_FAILED, // The socket failed to connect.
+ HE_SOCKET_ERROR, // An error occurred on a connected socket
+ HE_SHUTDOWN, // Http object is being destroyed
+ HE_OPERATION_CANCELLED, // Connection aborted locally
+ HE_AUTH, // Proxy Authentication Required
+ HE_CERTIFICATE_EXPIRED, // During SSL negotiation
+ HE_STREAM, // Problem reading or writing to the document
+ HE_CACHE, // Problem reading from cache
+ HE_DEFAULT
+};
+
+enum HttpHeader {
+ HH_AGE,
+ HH_CACHE_CONTROL,
+ HH_CONNECTION,
+ HH_CONTENT_DISPOSITION,
+ HH_CONTENT_LENGTH,
+ HH_CONTENT_RANGE,
+ HH_CONTENT_TYPE,
+ HH_COOKIE,
+ HH_DATE,
+ HH_ETAG,
+ HH_EXPIRES,
+ HH_HOST,
+ HH_IF_MODIFIED_SINCE,
+ HH_IF_NONE_MATCH,
+ HH_KEEP_ALIVE,
+ HH_LAST_MODIFIED,
+ HH_LOCATION,
+ HH_PROXY_AUTHENTICATE,
+ HH_PROXY_AUTHORIZATION,
+ HH_PROXY_CONNECTION,
+ HH_RANGE,
+ HH_SET_COOKIE,
+ HH_TE,
+ HH_TRAILERS,
+ HH_TRANSFER_ENCODING,
+ HH_UPGRADE,
+ HH_USER_AGENT,
+ HH_WWW_AUTHENTICATE,
+ HH_LAST = HH_WWW_AUTHENTICATE
+};
+
+const uint16 HTTP_DEFAULT_PORT = 80;
+const uint16 HTTP_SECURE_PORT = 443;
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) {
+ return (err != HE_NONE) ? err : def_err;
+}
+
+const char* ToString(HttpVersion version);
+bool FromString(HttpVersion& version, const std::string& str);
+
+const char* ToString(HttpVerb verb);
+bool FromString(HttpVerb& verb, const std::string& str);
+
+const char* ToString(HttpHeader header);
+bool FromString(HttpHeader& header, const std::string& str);
+
+inline bool HttpCodeIsInformational(uint32 code) { return ((code / 100) == 1); }
+inline bool HttpCodeIsSuccessful(uint32 code) { return ((code / 100) == 2); }
+inline bool HttpCodeIsRedirection(uint32 code) { return ((code / 100) == 3); }
+inline bool HttpCodeIsClientError(uint32 code) { return ((code / 100) == 4); }
+inline bool HttpCodeIsServerError(uint32 code) { return ((code / 100) == 5); }
+
+bool HttpCodeHasBody(uint32 code);
+bool HttpCodeIsCacheable(uint32 code);
+bool HttpHeaderIsEndToEnd(HttpHeader header);
+bool HttpHeaderIsCollapsible(HttpHeader header);
+
+struct HttpData;
+bool HttpShouldKeepAlive(const HttpData& data);
+
+typedef std::pair<std::string, std::string> HttpAttribute;
+typedef std::vector<HttpAttribute> HttpAttributeList;
+void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
+ std::string* composed);
+void HttpParseAttributes(const char * data, size_t len,
+ HttpAttributeList& attributes);
+bool HttpHasAttribute(const HttpAttributeList& attributes,
+ const std::string& name,
+ std::string* value);
+bool HttpHasNthAttribute(HttpAttributeList& attributes,
+ size_t index,
+ std::string* name,
+ std::string* value);
+
+// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp
+bool HttpDateToSeconds(const std::string& date, unsigned long* seconds);
+
+inline uint16 HttpDefaultPort(bool secure) {
+ return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT;
+}
+
+// Returns the http server notation for a given address
+std::string HttpAddress(const SocketAddress& address, bool secure);
+
+// functional for insensitive std::string compare
+struct iless {
+ bool operator()(const std::string& lhs, const std::string& rhs) const {
+ return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0);
+ }
+};
+
+// put quotes around a string and escape any quotes inside it
+std::string quote(const std::string& str);
+
+//////////////////////////////////////////////////////////////////////
+// Url
+//////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+class Url {
+public:
+ typedef typename Traits<CTYPE>::string string;
+
+ // TODO: Implement Encode/Decode
+ static int Encode(const CTYPE* source, CTYPE* destination, size_t len);
+ static int Encode(const string& source, string& destination);
+ static int Decode(const CTYPE* source, CTYPE* destination, size_t len);
+ static int Decode(const string& source, string& destination);
+
+ Url(const string& url) { do_set_url(url.c_str(), url.size()); }
+ Url(const string& path, const string& host, uint16 port = HTTP_DEFAULT_PORT)
+ : host_(host), port_(port), secure_(HTTP_SECURE_PORT == port)
+ { set_full_path(path); }
+
+ bool valid() const { return !host_.empty(); }
+ void clear() {
+ host_.clear();
+ port_ = HTTP_DEFAULT_PORT;
+ secure_ = false;
+ path_.assign(1, static_cast<CTYPE>('/'));
+ query_.clear();
+ }
+
+ void set_url(const string& val) {
+ do_set_url(val.c_str(), val.size());
+ }
+ string url() const {
+ string val; do_get_url(&val); return val;
+ }
+
+ void set_address(const string& val) {
+ do_set_address(val.c_str(), val.size());
+ }
+ string address() const {
+ string val; do_get_address(&val); return val;
+ }
+
+ void set_full_path(const string& val) {
+ do_set_full_path(val.c_str(), val.size());
+ }
+ string full_path() const {
+ string val; do_get_full_path(&val); return val;
+ }
+
+ void set_host(const string& val) { host_ = val; }
+ const string& host() const { return host_; }
+
+ void set_port(uint16 val) { port_ = val; }
+ uint16 port() const { return port_; }
+
+ void set_secure(bool val) { secure_ = val; }
+ bool secure() const { return secure_; }
+
+ void set_path(const string& val) {
+ if (val.empty()) {
+ path_.assign(1, static_cast<CTYPE>('/'));
+ } else {
+ ASSERT(val[0] == static_cast<CTYPE>('/'));
+ path_ = val;
+ }
+ }
+ const string& path() const { return path_; }
+
+ void set_query(const string& val) {
+ ASSERT(val.empty() || (val[0] == static_cast<CTYPE>('?')));
+ query_ = val;
+ }
+ const string& query() const { return query_; }
+
+ bool get_attribute(const string& name, string* value) const;
+
+private:
+ void do_set_url(const CTYPE* val, size_t len);
+ void do_set_address(const CTYPE* val, size_t len);
+ void do_set_full_path(const CTYPE* val, size_t len);
+
+ void do_get_url(string* val) const;
+ void do_get_address(string* val) const;
+ void do_get_full_path(string* val) const;
+
+ string host_, path_, query_;
+ uint16 port_;
+ bool secure_;
+};
+
+//////////////////////////////////////////////////////////////////////
+// HttpData
+//////////////////////////////////////////////////////////////////////
+
+struct HttpData {
+ typedef std::multimap<std::string, std::string, iless> HeaderMap;
+ typedef HeaderMap::const_iterator const_iterator;
+ typedef HeaderMap::iterator iterator;
+
+ HttpVersion version;
+ scoped_ptr<StreamInterface> document;
+
+ HttpData() : version(HVER_1_1) { }
+
+ enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW };
+ void changeHeader(const std::string& name, const std::string& value,
+ HeaderCombine combine);
+ inline void addHeader(const std::string& name, const std::string& value,
+ bool append = true) {
+ changeHeader(name, value, append ? HC_AUTO : HC_NO);
+ }
+ inline void setHeader(const std::string& name, const std::string& value,
+ bool overwrite = true) {
+ changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW);
+ }
+ // Returns count of erased headers
+ size_t clearHeader(const std::string& name);
+ // Returns iterator to next header
+ iterator clearHeader(iterator header);
+
+ // keep in mind, this may not do what you want in the face of multiple headers
+ bool hasHeader(const std::string& name, std::string* value) const;
+
+ inline const_iterator begin() const {
+ return headers_.begin();
+ }
+ inline const_iterator end() const {
+ return headers_.end();
+ }
+ inline iterator begin() {
+ return headers_.begin();
+ }
+ inline iterator end() {
+ return headers_.end();
+ }
+ inline const_iterator begin(const std::string& name) const {
+ return headers_.lower_bound(name);
+ }
+ inline const_iterator end(const std::string& name) const {
+ return headers_.upper_bound(name);
+ }
+ inline iterator begin(const std::string& name) {
+ return headers_.lower_bound(name);
+ }
+ inline iterator end(const std::string& name) {
+ return headers_.upper_bound(name);
+ }
+
+ // Convenience methods using HttpHeader
+ inline void changeHeader(HttpHeader header, const std::string& value,
+ HeaderCombine combine) {
+ changeHeader(ToString(header), value, combine);
+ }
+ inline void addHeader(HttpHeader header, const std::string& value,
+ bool append = true) {
+ addHeader(ToString(header), value, append);
+ }
+ inline void setHeader(HttpHeader header, const std::string& value,
+ bool overwrite = true) {
+ setHeader(ToString(header), value, overwrite);
+ }
+ inline void clearHeader(HttpHeader header) {
+ clearHeader(ToString(header));
+ }
+ inline bool hasHeader(HttpHeader header, std::string* value) const {
+ return hasHeader(ToString(header), value);
+ }
+ inline const_iterator begin(HttpHeader header) const {
+ return headers_.lower_bound(ToString(header));
+ }
+ inline const_iterator end(HttpHeader header) const {
+ return headers_.upper_bound(ToString(header));
+ }
+ inline iterator begin(HttpHeader header) {
+ return headers_.lower_bound(ToString(header));
+ }
+ inline iterator end(HttpHeader header) {
+ return headers_.upper_bound(ToString(header));
+ }
+
+ void setContent(const std::string& content_type, StreamInterface* document);
+ void setDocumentAndLength(StreamInterface* document);
+
+ virtual size_t formatLeader(char* buffer, size_t size) const = 0;
+ virtual HttpError parseLeader(const char* line, size_t len) = 0;
+
+protected:
+ virtual ~HttpData() { }
+ void clear(bool release_document);
+ void copy(const HttpData& src);
+
+private:
+ HeaderMap headers_;
+};
+
+struct HttpRequestData : public HttpData {
+ HttpVerb verb;
+ std::string path;
+
+ HttpRequestData() : verb(HV_GET) { }
+
+ void clear(bool release_document);
+ void copy(const HttpRequestData& src);
+
+ virtual size_t formatLeader(char* buffer, size_t size) const;
+ virtual HttpError parseLeader(const char* line, size_t len);
+
+ bool getAbsoluteUri(std::string* uri) const;
+ bool getRelativeUri(std::string* host, std::string* path) const;
+};
+
+struct HttpResponseData : public HttpData {
+ uint32 scode;
+ std::string message;
+
+ HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { }
+ void clear(bool release_document);
+ void copy(const HttpResponseData& src);
+
+ // Convenience methods
+ void set_success(uint32 scode = HC_OK);
+ void set_success(const std::string& content_type, StreamInterface* document,
+ uint32 scode = HC_OK);
+ void set_redirect(const std::string& location,
+ uint32 scode = HC_MOVED_TEMPORARILY);
+ void set_error(uint32 scode);
+
+ virtual size_t formatLeader(char* buffer, size_t size) const;
+ virtual HttpError parseLeader(const char* line, size_t len);
+};
+
+struct HttpTransaction {
+ HttpRequestData request;
+ HttpResponseData response;
+};
+
+//////////////////////////////////////////////////////////////////////
+// Http Authentication
+//////////////////////////////////////////////////////////////////////
+
+struct HttpAuthContext {
+ std::string auth_method;
+ HttpAuthContext(const std::string& auth) : auth_method(auth) { }
+ virtual ~HttpAuthContext() { }
+};
+
+enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR };
+
+// 'context' is used by this function to record information between calls.
+// Start by passing a null pointer, then pass the same pointer each additional
+// call. When the authentication attempt is finished, delete the context.
+HttpAuthResult HttpAuthenticate(
+ const char * challenge, size_t len,
+ const SocketAddress& server,
+ const std::string& method, const std::string& uri,
+ const std::string& username, const CryptString& password,
+ HttpAuthContext *& context, std::string& response, std::string& auth_method);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_HTTPCOMMON_H__
diff --git a/talk/base/httprequest.cc b/talk/base/httprequest.cc
new file mode 100644
index 0000000..48c924e
--- /dev/null
+++ b/talk/base/httprequest.cc
@@ -0,0 +1,127 @@
+/*
+ * libjingle
+ * Copyright 2006, 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/httprequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/firewallsocketserver.h"
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/ssladapter.h"
+
+using namespace talk_base;
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+HttpMonitor::HttpMonitor(SocketServer *ss) {
+ ASSERT(Thread::Current() != NULL);
+ ss_ = ss;
+ reset();
+}
+
+void HttpMonitor::Connect(HttpClient *http) {
+ http->SignalHttpClientComplete.connect(this,
+ &HttpMonitor::OnHttpClientComplete);
+}
+
+void HttpMonitor::OnHttpClientComplete(HttpClient * http, HttpErrorType error) {
+ complete_ = true;
+ error_ = error;
+ ss_->WakeUp();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec
+
+HttpRequest::HttpRequest(const std::string &user_agent)
+ : firewall_(0), port_(80), secure_(false),
+ timeout_(kDefaultHTTPTimeout), fail_redirect_(false),
+ client_(user_agent.c_str(), NULL), error_(HE_NONE) {
+}
+
+void HttpRequest::Send() {
+ // TODO: Rewrite this to use the thread's native socket server, and a more
+ // natural flow?
+
+ PhysicalSocketServer physical;
+ SocketServer * ss = &physical;
+ if (firewall_) {
+ ss = new FirewallSocketServer(ss, firewall_);
+ }
+
+ SslSocketFactory factory(ss, client_.agent());
+ factory.SetProxy(proxy_);
+ if (secure_)
+ factory.UseSSL(host_.c_str());
+
+ //factory.SetLogging("HttpRequest");
+
+ ReuseSocketPool pool(&factory);
+ client_.set_pool(&pool);
+
+ bool transparent_proxy = (port_ == 80) && ((proxy_.type == PROXY_HTTPS) ||
+ (proxy_.type == PROXY_UNKNOWN));
+
+ if (transparent_proxy) {
+ client_.set_proxy(proxy_);
+ }
+ client_.set_fail_redirect(fail_redirect_);
+
+ SocketAddress server(host_, port_);
+ client_.set_server(server);
+
+ LOG(LS_INFO) << "HttpRequest start: " << host_ + client_.request().path;
+
+ HttpMonitor monitor(ss);
+ monitor.Connect(&client_);
+ client_.start();
+ ss->Wait(timeout_, true);
+ if (!monitor.done()) {
+ LOG(LS_INFO) << "HttpRequest request timed out";
+ client_.reset();
+ return;
+ }
+
+ set_error(monitor.error());
+ if (error_) {
+ LOG(LS_INFO) << "HttpRequest request error: " << error_;
+ return;
+ }
+
+ std::string value;
+ if (client_.response().hasHeader(HH_LOCATION, &value)) {
+ response_redirect_ = value.c_str();
+ }
+}
diff --git a/talk/base/httprequest.h b/talk/base/httprequest.h
new file mode 100644
index 0000000..2e13c32
--- /dev/null
+++ b/talk/base/httprequest.h
@@ -0,0 +1,132 @@
+/*
+ * libjingle
+ * Copyright 2006, 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 _HTTPREQUEST_H_
+#define _HTTPREQUEST_H_
+
+#include "talk/base/httpclient.h"
+#include "talk/base/logging.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/thread.h"
+#include "talk/base/sslsocketfactory.h" // Deprecated include
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpRequest
+///////////////////////////////////////////////////////////////////////////////
+
+class FirewallManager;
+class MemoryStream;
+
+class HttpRequest {
+public:
+ HttpRequest(const std::string &user_agent);
+
+ void Send();
+
+ void set_proxy(const ProxyInfo& proxy) {
+ proxy_ = proxy;
+ }
+ void set_firewall(FirewallManager * firewall) {
+ firewall_ = firewall;
+ }
+
+ // The DNS name of the host to connect to.
+ const std::string& host() { return host_; }
+ void set_host(const std::string& host) { host_ = host; }
+
+ // The port to connect to on the target host.
+ int port() { return port_; }
+ void set_port(int port) { port_ = port; }
+
+ // Whether the request should use SSL.
+ bool secure() { return secure_; }
+ void set_secure(bool secure) { secure_ = secure; }
+
+ // Returns the redirect when redirection occurs
+ const std::string& response_redirect() { return response_redirect_; }
+
+ // Time to wait on the download, in ms. Default is 5000 (5s)
+ int timeout() { return timeout_; }
+ void set_timeout(int timeout) { timeout_ = timeout; }
+
+ // Fail redirects to allow analysis of redirect urls, etc.
+ bool fail_redirect() const { return fail_redirect_; }
+ void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; }
+
+ HttpRequestData& request() { return client_.request(); }
+ HttpResponseData& response() { return client_.response(); }
+ HttpErrorType error() { return error_; }
+
+protected:
+ void set_error(HttpErrorType error) { error_ = error; }
+
+private:
+ ProxyInfo proxy_;
+ FirewallManager * firewall_;
+ std::string host_;
+ int port_;
+ bool secure_;
+ int timeout_;
+ bool fail_redirect_;
+ HttpClient client_;
+ HttpErrorType error_;
+ std::string response_redirect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// HttpMonitor
+///////////////////////////////////////////////////////////////////////////////
+
+class HttpMonitor : public sigslot::has_slots<> {
+public:
+ HttpMonitor(SocketServer *ss);
+
+ void reset() {
+ complete_ = false;
+ error_ = HE_DEFAULT;
+ }
+
+ bool done() const { return complete_; }
+ HttpErrorType error() const { return error_; }
+
+ void Connect(HttpClient* http);
+ void OnHttpClientComplete(HttpClient * http, HttpErrorType error);
+
+private:
+ bool complete_;
+ HttpErrorType error_;
+ SocketServer *ss_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base_
+
+#endif // _HTTPREQUEST_H_
diff --git a/talk/base/latebindingsymboltable.cc b/talk/base/latebindingsymboltable.cc
new file mode 100644
index 0000000..f9d59ab
--- /dev/null
+++ b/talk/base/latebindingsymboltable.cc
@@ -0,0 +1,111 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/latebindingsymboltable.h"
+
+#ifdef LINUX
+#include <dlfcn.h>
+#endif
+
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+inline static const char *GetDllError() {
+#ifdef LINUX
+ char *err = dlerror();
+ if (err) {
+ return err;
+ } else {
+ return "No error";
+ }
+#else
+#error Not implemented
+#endif
+}
+
+DllHandle InternalLoadDll(const char dll_name[]) {
+#ifdef LINUX
+ DllHandle handle = dlopen(dll_name, RTLD_NOW);
+#else
+#error Not implemented
+#endif
+ if (handle == kInvalidDllHandle) {
+ LOG(LS_WARNING) << "Can't load " << dll_name << ": " << GetDllError();
+ }
+ return handle;
+}
+
+void InternalUnloadDll(DllHandle handle) {
+#ifdef LINUX
+ if (dlclose(handle) != 0) {
+ LOG(LS_ERROR) << GetDllError();
+ }
+#else
+#error Not implemented
+#endif
+}
+
+static bool LoadSymbol(DllHandle handle,
+ const char *symbol_name,
+ void **symbol) {
+#ifdef LINUX
+ *symbol = dlsym(handle, symbol_name);
+ char *err = dlerror();
+ if (err) {
+ LOG(LS_ERROR) << "Error loading symbol " << symbol_name << ": " << err;
+ return false;
+ } else if (!*symbol) {
+ LOG(LS_ERROR) << "Symbol " << symbol_name << " is NULL";
+ return false;
+ }
+ return true;
+#else
+#error Not implemented
+#endif
+}
+
+// This routine MUST assign SOME value for every symbol, even if that value is
+// NULL, or else some symbols may be left with uninitialized data that the
+// caller may later interpret as a valid address.
+bool InternalLoadSymbols(DllHandle handle,
+ int num_symbols,
+ const char *const symbol_names[],
+ void *symbols[]) {
+#ifdef LINUX
+ // Clear any old errors.
+ dlerror();
+#endif
+ for (int i = 0; i < num_symbols; ++i) {
+ if (!LoadSymbol(handle, symbol_names[i], &symbols[i])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace talk_base
diff --git a/talk/base/latebindingsymboltable.h b/talk/base/latebindingsymboltable.h
new file mode 100644
index 0000000..994c26a
--- /dev/null
+++ b/talk/base/latebindingsymboltable.h
@@ -0,0 +1,193 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_LATEBINDINGSYMBOLTABLE_H_
+#define TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
+
+#include <stddef.h> // for NULL
+#include <string.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+// This file provides macros for creating "symbol table" classes to simplify the
+// dynamic loading of symbols from DLLs. Currently the implementation only
+// supports Linux and pure C symbols.
+// See talk/sound/pulseaudiosymboltable.(h|cc) for an example.
+
+namespace talk_base {
+
+#ifdef LINUX
+typedef void *DllHandle;
+
+const DllHandle kInvalidDllHandle = NULL;
+#else
+#error Not implemented
+#endif
+
+// These are helpers for use only by the class below.
+DllHandle InternalLoadDll(const char dll_name[]);
+
+void InternalUnloadDll(DllHandle handle);
+
+bool InternalLoadSymbols(DllHandle handle,
+ int num_symbols,
+ const char *const symbol_names[],
+ void *symbols[]);
+
+template <int SYMBOL_TABLE_SIZE,
+ const char kDllName[],
+ const char *const kSymbolNames[]>
+class LateBindingSymbolTable {
+ public:
+ LateBindingSymbolTable()
+ : handle_(kInvalidDllHandle),
+ undefined_symbols_(false) {
+ memset(symbols_, 0, sizeof(symbols_));
+ }
+
+ ~LateBindingSymbolTable() {
+ Unload();
+ }
+
+ static int NumSymbols() {
+ return SYMBOL_TABLE_SIZE;
+ }
+
+ // We do not use this, but we offer it for theoretical convenience.
+ static const char *GetSymbolName(int index) {
+ ASSERT(index < NumSymbols());
+ return kSymbolNames[index];
+ }
+
+ bool IsLoaded() const {
+ return handle_ != kInvalidDllHandle;
+ }
+
+ // Loads the DLL and the symbol table. Returns true iff the DLL and symbol
+ // table loaded successfully.
+ bool Load() {
+ if (IsLoaded()) {
+ return true;
+ }
+ if (undefined_symbols_) {
+ // We do not attempt to load again because repeated attempts are not
+ // likely to succeed and DLL loading is costly.
+ LOG(LS_ERROR) << "We know there are undefined symbols";
+ return false;
+ }
+ handle_ = InternalLoadDll(kDllName);
+ if (!IsLoaded()) {
+ return false;
+ }
+ if (!InternalLoadSymbols(handle_, NumSymbols(), kSymbolNames, symbols_)) {
+ undefined_symbols_ = true;
+ Unload();
+ return false;
+ }
+ return true;
+ }
+
+ void Unload() {
+ if (!IsLoaded()) {
+ return;
+ }
+ InternalUnloadDll(handle_);
+ handle_ = kInvalidDllHandle;
+ memset(symbols_, 0, sizeof(symbols_));
+ }
+
+ // Retrieves the given symbol. NOTE: Recommended to use LATESYM_GET below
+ // instead of this.
+ void *GetSymbol(int index) const {
+ ASSERT(IsLoaded());
+ ASSERT(index < NumSymbols());
+ return symbols_[index];
+ }
+
+ private:
+ DllHandle handle_;
+ bool undefined_symbols_;
+ void *symbols_[SYMBOL_TABLE_SIZE];
+
+ DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable);
+};
+
+// This macro must be invoked in a header to declare a symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(ClassName) \
+enum {
+
+// This macro must be invoked in the header declaration once for each symbol
+// (recommended to use an X-Macro to avoid duplication).
+// This macro defines an enum with names built from the symbols, which
+// essentially creates a hash table in the compiler from symbol names to their
+// indices in the symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(ClassName, sym) \
+ ClassName##_SYMBOL_TABLE_INDEX_##sym,
+
+// This macro completes the header declaration.
+#define LATE_BINDING_SYMBOL_TABLE_DECLARE_END(ClassName) \
+ ClassName##_SYMBOL_TABLE_SIZE \
+}; \
+\
+extern const char ClassName##_kDllName[]; \
+extern const char *const \
+ ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE]; \
+\
+typedef ::talk_base::LateBindingSymbolTable<ClassName##_SYMBOL_TABLE_SIZE, \
+ ClassName##_kDllName, \
+ ClassName##_kSymbolNames> \
+ ClassName;
+
+// This macro must be invoked in a .cc file to define a previously-declared
+// symbol table class.
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(ClassName, dllName) \
+const char ClassName##_kDllName[] = dllName; \
+const char *const ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE] = {
+
+// This macro must be invoked in the .cc definition once for each symbol
+// (recommended to use an X-Macro to avoid duplication).
+// This would have to use the mangled name if we were to ever support C++
+// symbols.
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(ClassName, sym) \
+ #sym,
+
+#define LATE_BINDING_SYMBOL_TABLE_DEFINE_END(ClassName) \
+};
+
+// Index of a given symbol in the given symbol table class.
+#define LATESYM_INDEXOF(ClassName, sym) \
+ (ClassName##_SYMBOL_TABLE_INDEX_##sym)
+
+// Returns a reference to the given late-binded symbol, with the correct type.
+#define LATESYM_GET(ClassName, inst, sym) \
+ (*reinterpret_cast<typeof(&sym)>( \
+ (inst)->GetSymbol(LATESYM_INDEXOF(ClassName, sym))))
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LATEBINDINGSYMBOLTABLE_H_
diff --git a/talk/base/linked_ptr.h b/talk/base/linked_ptr.h
new file mode 100644
index 0000000..a98a367
--- /dev/null
+++ b/talk/base/linked_ptr.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+/*
+ * linked_ptr - simple reference linked pointer
+ * (like reference counting, just using a linked list of the references
+ * instead of their count.)
+ *
+ * The implementation stores three pointers for every linked_ptr, but
+ * does not allocate anything on the free store.
+ */
+
+#ifndef TALK_BASE_LINKED_PTR_H__
+#define TALK_BASE_LINKED_PTR_H__
+
+namespace talk_base {
+
+/* For ANSI-challenged compilers, you may want to #define
+ * NO_MEMBER_TEMPLATES, explicit or mutable */
+#define NO_MEMBER_TEMPLATES
+
+template <class X> class linked_ptr
+{
+public:
+
+#ifndef NO_MEMBER_TEMPLATES
+# define TEMPLATE_FUNCTION template <class Y>
+ TEMPLATE_FUNCTION friend class linked_ptr<Y>;
+#else
+# define TEMPLATE_FUNCTION
+ typedef X Y;
+#endif
+
+ typedef X element_type;
+
+ explicit linked_ptr(X* p = 0) throw()
+ : itsPtr(p) {itsPrev = itsNext = this;}
+ ~linked_ptr()
+ {release();}
+ linked_ptr(const linked_ptr& r) throw()
+ {acquire(r);}
+ linked_ptr& operator=(const linked_ptr& r)
+ {
+ if (this != &r) {
+ release();
+ acquire(r);
+ }
+ return *this;
+ }
+
+#ifndef NO_MEMBER_TEMPLATES
+ template <class Y> friend class linked_ptr<Y>;
+ template <class Y> linked_ptr(const linked_ptr<Y>& r) throw()
+ {acquire(r);}
+ template <class Y> linked_ptr& operator=(const linked_ptr<Y>& r)
+ {
+ if (this != &r) {
+ release();
+ acquire(r);
+ }
+ return *this;
+ }
+#endif // NO_MEMBER_TEMPLATES
+
+ X& operator*() const throw() {return *itsPtr;}
+ X* operator->() const throw() {return itsPtr;}
+ X* get() const throw() {return itsPtr;}
+ bool unique() const throw() {return itsPrev ? itsPrev==this : true;}
+
+private:
+ X* itsPtr;
+ mutable const linked_ptr* itsPrev;
+ mutable const linked_ptr* itsNext;
+
+ void acquire(const linked_ptr& r) throw()
+ { // insert this to the list
+ itsPtr = r.itsPtr;
+ itsNext = r.itsNext;
+ itsNext->itsPrev = this;
+ itsPrev = &r;
+#ifndef mutable
+ r.itsNext = this;
+#else // for ANSI-challenged compilers
+ (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+ }
+
+#ifndef NO_MEMBER_TEMPLATES
+ template <class Y> void acquire(const linked_ptr<Y>& r) throw()
+ { // insert this to the list
+ itsPtr = r.itsPtr;
+ itsNext = r.itsNext;
+ itsNext->itsPrev = this;
+ itsPrev = &r;
+#ifndef mutable
+ r.itsNext = this;
+#else // for ANSI-challenged compilers
+ (const_cast<linked_ptr<X>*>(&r))->itsNext = this;
+#endif
+ }
+#endif // NO_MEMBER_TEMPLATES
+
+ void release()
+ { // erase this from the list, delete if unique
+ if (unique()) delete itsPtr;
+ else {
+ itsPrev->itsNext = itsNext;
+ itsNext->itsPrev = itsPrev;
+ itsPrev = itsNext = 0;
+ }
+ itsPtr = 0;
+ }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LINKED_PTR_H__
+
diff --git a/talk/base/linux.cc b/talk/base/linux.cc
new file mode 100644
index 0000000..5ae103e
--- /dev/null
+++ b/talk/base/linux.cc
@@ -0,0 +1,285 @@
+/*
+ * 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.
+ */
+
+#ifdef LINUX
+#include "talk/base/linux.h"
+
+#include <errno.h>
+#include <sys/utsname.h>
+
+#include <cstdio>
+
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+static const char kCpuInfoFile[] = "/proc/cpuinfo";
+static const char kCpuMaxFreqFile[] =
+ "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq";
+
+ProcCpuInfo::ProcCpuInfo() {
+}
+
+ProcCpuInfo::~ProcCpuInfo() {
+}
+
+bool ProcCpuInfo::LoadFromSystem() {
+ ConfigParser procfs;
+ if (!procfs.Open(kCpuInfoFile)) {
+ return false;
+ }
+ return procfs.Parse(&cpu_info_);
+};
+
+bool ProcCpuInfo::GetNumCpus(int *num) {
+ if (cpu_info_.size() == 0) {
+ return false;
+ }
+ *num = cpu_info_.size();
+ return true;
+}
+
+bool ProcCpuInfo::GetNumPhysicalCpus(int *num) {
+ if (cpu_info_.size() == 0) {
+ return false;
+ }
+ int total_cores = 0;
+ int physical_id_prev = -1;
+ int cpus = static_cast<int>(cpu_info_.size());
+ for (int i = 0; i < cpus; ++i) {
+ int physical_id;
+ if (GetCpuIntValue(i, "physical id", &physical_id)) {
+ if (physical_id != physical_id_prev) {
+ physical_id_prev = physical_id;
+ int cores;
+ if (GetCpuIntValue(i, "cpu cores", &cores)) {
+ total_cores += cores;
+ }
+ }
+ }
+ }
+ return total_cores;
+}
+
+bool ProcCpuInfo::GetCpuStringValue(int cpu_id, const std::string& key,
+ std::string *result) {
+ if (cpu_id >= static_cast<int>(cpu_info_.size()))
+ return false;
+ ConfigParser::SimpleMap::iterator iter = cpu_info_[cpu_id].find(key);
+ if (iter == cpu_info_[cpu_id].end()) {
+ return false;
+ }
+ *result = iter->second;
+ return true;
+}
+
+bool ProcCpuInfo::GetCpuIntValue(int cpu_id, const std::string& key,
+ int *result) {
+ if (cpu_id >= static_cast<int>(cpu_info_.size())) {
+ return false;
+ }
+ ConfigParser::SimpleMap::iterator iter = cpu_info_[cpu_id].find(key);
+ if (iter == cpu_info_[cpu_id].end()) {
+ return false;
+ }
+ *result = atoi((iter->second).c_str());
+ return true;
+}
+
+ConfigParser::ConfigParser() {}
+
+ConfigParser::~ConfigParser() {}
+
+bool ConfigParser::Open(const std::string& filename) {
+ FileStream *fs = new FileStream();
+ if (!fs->Open(filename, "r")) {
+ return false;
+ }
+ instream_.reset(fs);
+ return true;
+}
+
+void ConfigParser::Attach(StreamInterface* stream) {
+ instream_.reset(stream);
+}
+
+bool ConfigParser::Parse(MapVector *key_val_pairs) {
+ // Parses the file and places the found key-value pairs into key_val_pairs.
+ SimpleMap section;
+ while (ParseSection(§ion)) {
+ key_val_pairs->push_back(section);
+ section.clear();
+ }
+ return (!key_val_pairs->empty());
+}
+
+bool ConfigParser::ParseSection(SimpleMap *key_val_pair) {
+ // Parses the next section in the filestream and places the found key-value
+ // pairs into key_val_pair.
+ std::string key, value;
+ while (ParseLine(&key, &value)) {
+ (*key_val_pair)[key] = value;
+ }
+ return (!key_val_pair->empty());
+}
+
+bool ConfigParser::ParseLine(std::string *key, std::string *value) {
+ // Parses the next line in the filestream and places the found key-value
+ // pair into key and val.
+ std::string line;
+ if ((instream_->ReadLine(&line)) == EOF) {
+ return false;
+ }
+ std::vector<std::string> tokens;
+ if (2 != split(line, ':', &tokens)) {
+ return false;
+ }
+ // Removes whitespace at the end of Key name
+ size_t pos = tokens[0].length() - 1;
+ while ((pos > 0) && isspace(tokens[0][pos])) {
+ pos--;
+ }
+ tokens[0].erase(pos + 1);
+ // Removes whitespace at the start of value
+ pos = 0;
+ while (pos < tokens[1].length() && isspace(tokens[1][pos])) {
+ pos++;
+ }
+ tokens[1].erase(0, pos);
+ *key = tokens[0];
+ *value = tokens[1];
+ return true;
+}
+
+static bool ExpectLineFromStream(FileStream *stream,
+ std::string *out) {
+ StreamResult res = stream->ReadLine(out);
+ if (res != SR_SUCCESS) {
+ if (res != SR_EOS) {
+ LOG(LS_ERROR) << "Error when reading from stream";
+ } else {
+ LOG(LS_ERROR) << "Incorrect number of lines in stream";
+ }
+ return false;
+ }
+ return true;
+}
+
+static void ExpectEofFromStream(FileStream *stream) {
+ std::string unused;
+ StreamResult res = stream->ReadLine(&unused);
+ if (res == SR_SUCCESS) {
+ LOG(LS_WARNING) << "Ignoring unexpected extra lines from stream";
+ } else if (res != SR_EOS) {
+ LOG(LS_WARNING) << "Error when checking for extra lines from stream";
+ }
+}
+
+// For caching the lsb_release output (reading it invokes a sub-process and
+// hence is somewhat expensive).
+static std::string lsb_release_string;
+static CriticalSection lsb_release_string_critsec;
+
+std::string ReadLinuxLsbRelease() {
+ CritScope cs(&lsb_release_string_critsec);
+ if (!lsb_release_string.empty()) {
+ // Have cached result from previous call.
+ return lsb_release_string;
+ }
+ // No cached result. Run lsb_release and parse output.
+ POpenStream lsb_release_output;
+ if (!lsb_release_output.Open("lsb_release -idrcs", "r")) {
+ LOG_ERR(LS_ERROR) << "Can't run lsb_release";
+ return lsb_release_string; // empty
+ }
+ // Read in the command's output and build the string.
+ std::ostringstream sstr;
+ std::string line;
+ int wait_status;
+
+ if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+ return lsb_release_string; // empty
+ }
+ sstr << "DISTRIB_ID=" << line;
+
+ if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+ return lsb_release_string; // empty
+ }
+ sstr << " DISTRIB_DESCRIPTION=\"" << line << '"';
+
+ if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+ return lsb_release_string; // empty
+ }
+ sstr << " DISTRIB_RELEASE=" << line;
+
+ if (!ExpectLineFromStream(&lsb_release_output, &line)) {
+ return lsb_release_string; // empty
+ }
+ sstr << " DISTRIB_CODENAME=" << line;
+
+ // Should not be anything left.
+ ExpectEofFromStream(&lsb_release_output);
+
+ lsb_release_output.Close();
+ wait_status = lsb_release_output.GetWaitStatus();
+ if (wait_status == -1 ||
+ !WIFEXITED(wait_status) ||
+ WEXITSTATUS(wait_status) != 0) {
+ LOG(LS_WARNING) << "Unexpected exit status from lsb_release";
+ }
+
+ lsb_release_string = sstr.str();
+
+ return lsb_release_string;
+}
+
+std::string ReadLinuxUname() {
+ struct utsname buf;
+ if (uname(&buf) < 0) {
+ LOG_ERR(LS_ERROR) << "Can't call uname()";
+ return std::string();
+ }
+ std::ostringstream sstr;
+ sstr << buf.sysname << " "
+ << buf.release << " "
+ << buf.version << " "
+ << buf.machine;
+ return sstr.str();
+}
+
+int ReadCpuMaxFreq() {
+ FileStream fs;
+ std::string str;
+ if (!fs.Open(kCpuMaxFreqFile, "r") || SR_SUCCESS != fs.ReadLine(&str)) {
+ return -1;
+ }
+ return atoi(str.c_str());
+}
+
+} // namespace talk_base
+
+#endif // LINUX
diff --git a/talk/base/linux.h b/talk/base/linux.h
new file mode 100644
index 0000000..e56eb95
--- /dev/null
+++ b/talk/base/linux.h
@@ -0,0 +1,129 @@
+/*
+ * 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_BASE_LINUX_H_
+#define TALK_BASE_LINUX_H_
+
+#ifdef LINUX
+#include <string>
+#include <map>
+#include <vector>
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////////////
+// ConfigParser parses a FileStream of an ".ini."-type format into a map.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+// ConfigParser parser;
+// ConfigParser::MapVector key_val_pairs;
+// if (parser.Open(inifile) && parser.Parse(&key_val_pairs)) {
+// for (int section_num=0; i < key_val_pairs.size(); ++section_num) {
+// std::string val1 = key_val_pairs[section_num][key1];
+// std::string val2 = key_val_pairs[section_num][key2];
+// // Do something with valn;
+// }
+// }
+
+class ConfigParser {
+ public:
+ typedef std::map<std::string, std::string> SimpleMap;
+ typedef std::vector<SimpleMap> MapVector;
+
+ ConfigParser();
+ virtual ~ConfigParser();
+
+ virtual bool Open(const std::string& filename);
+ virtual void Attach(StreamInterface* stream);
+ virtual bool Parse(MapVector *key_val_pairs);
+ virtual bool ParseSection(SimpleMap *key_val_pair);
+ virtual bool ParseLine(std::string *key, std::string *value);
+
+ private:
+ scoped_ptr<StreamInterface> instream_;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+// ProcCpuInfo reads CPU info from the /proc subsystem on any *NIX platform.
+//////////////////////////////////////////////////////////////////////////////
+
+// Sample Usage:
+// ProcCpuInfo proc_info;
+// int no_of_cpu;
+// if (proc_info.LoadFromSystem()) {
+// std::string out_str;
+// proc_info.GetNumCpus(&no_of_cpu);
+// proc_info.GetCpuStringValue(0, "vendor_id", &out_str);
+// }
+// }
+
+class ProcCpuInfo {
+ public:
+ ProcCpuInfo();
+ virtual ~ProcCpuInfo();
+
+ // Reads the proc subsystem's cpu info into memory. If this fails, this
+ // returns false; if it succeeds, it returns true.
+ virtual bool LoadFromSystem();
+
+ // Obtains the number of logical CPU threads and places the value num.
+ virtual bool GetNumCpus(int *num);
+
+ // Obtains the number of physical CPU cores and places the value num.
+ virtual bool GetNumPhysicalCpus(int *num);
+
+ // Looks for the CPU proc item with the given name for the given CPU number
+ // and places the string value in result.
+ virtual bool GetCpuStringValue(int cpu_id, const std::string& key,
+ std::string *result);
+
+ // Looks for the CPU proc item with the given name for the given CPU number
+ // and places the int value in result.
+ virtual bool GetCpuIntValue(int cpu_id, const std::string& key,
+ int *result);
+
+ private:
+ ConfigParser::MapVector cpu_info_;
+};
+
+// Builds a string containing the info from lsb_release on a single line.
+std::string ReadLinuxLsbRelease();
+
+// Returns the output of "uname".
+std::string ReadLinuxUname();
+
+// Returns the content (int) of
+// /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
+// Returns -1 on error.
+int ReadCpuMaxFreq();
+
+} // namespace talk_base
+
+#endif // LINUX
+#endif // TALK_BASE_LINUX_H_
diff --git a/talk/base/logging.cc b/talk/base/logging.cc
new file mode 100644
index 0000000..3515312
--- /dev/null
+++ b/talk/base/logging.cc
@@ -0,0 +1,612 @@
+/*
+ * 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.
+ */
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#define snprintf _snprintf
+#undef ERROR // wingdi.h
+#endif
+
+#ifdef OSX
+#include <CoreServices/CoreServices.h>
+#elif defined(ANDROID)
+#include <android/log.h>
+static const char kLibjingle[] = "libjingle";
+// Android has a 1024 limit on log inputs. We use 60 chars as an
+// approx for the header/tag portion.
+// See android/system/core/liblog/logd_write.c
+static const int kMaxLogLineSize = 1024 - 60;
+#endif // OSX || ANDROID
+
+#include <iostream>
+#include <iomanip>
+#include <vector>
+
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// Constant Labels
+/////////////////////////////////////////////////////////////////////////////
+
+const char * FindLabel(int value, const ConstantLabel entries[]) {
+ for (int i = 0; entries[i].label; ++i) {
+ if (value == entries[i].value) {
+ return entries[i].label;
+ }
+ }
+ return 0;
+}
+
+std::string ErrorName(int err, const ConstantLabel * err_table) {
+ if (err == 0)
+ return "No error";
+
+ if (err_table != 0) {
+ if (const char * value = FindLabel(err, err_table))
+ return value;
+ }
+
+ char buffer[16];
+ snprintf(buffer, sizeof(buffer), "0x%08x", err);
+ return buffer;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// LogMessage
+/////////////////////////////////////////////////////////////////////////////
+
+const int LogMessage::NO_LOGGING = LS_ERROR + 1;
+
+#if _DEBUG
+static const int LOG_DEFAULT = LS_INFO;
+#else // !_DEBUG
+static const int LOG_DEFAULT = LogMessage::NO_LOGGING;
+#endif // !_DEBUG
+
+// Global lock for log subsystem, only needed to serialize access to streams_.
+CriticalSection LogMessage::crit_;
+
+// By default, release builds don't log, debug builds at info level
+int LogMessage::min_sev_ = LOG_DEFAULT;
+int LogMessage::dbg_sev_ = LOG_DEFAULT;
+
+// Don't bother printing context for the ubiquitous INFO log messages
+int LogMessage::ctx_sev_ = LS_WARNING;
+
+// The list of logging streams currently configured.
+// Note: we explicitly do not clean this up, because of the uncertain ordering
+// of destructors at program exit. Let the person who sets the stream trigger
+// cleanup by setting to NULL, or let it leak (safe at program exit).
+LogMessage::StreamList LogMessage::streams_;
+
+// Boolean options default to false (0)
+bool LogMessage::thread_, LogMessage::timestamp_;
+
+// Program start time
+uint32 LogMessage::start_ = StartTime();
+
+// If we're in diagnostic mode, we'll be explicitly set that way; default=false.
+bool LogMessage::is_diagnostic_mode_ = false;
+
+LogMessage::LogMessage(const char* file, int line, LoggingSeverity sev,
+ LogErrorContext err_ctx, int err, const char* module)
+ : severity_(sev) {
+ // Android's logging facility keeps track of timestamp and thread.
+#ifndef ANDROID
+ if (timestamp_) {
+ uint32 time = TimeSince(start_);
+ print_stream_ << "[" << std::setfill('0') << std::setw(3) << (time / 1000)
+ << ":" << std::setw(3) << (time % 1000) << std::setfill(' ')
+ << "] ";
+ }
+
+ if (thread_) {
+#ifdef WIN32
+ DWORD id = GetCurrentThreadId();
+ print_stream_ << "[" << std::hex << id << std::dec << "] ";
+#endif // WIN32
+ }
+#endif // !ANDROID
+
+ if (severity_ >= ctx_sev_) {
+ print_stream_ << Describe(sev) << "(" << DescribeFile(file)
+ << ":" << line << "): ";
+ }
+
+ if (err_ctx != ERRCTX_NONE) {
+ std::ostringstream tmp;
+ tmp << "[0x" << std::setfill('0') << std::hex << std::setw(8) << err << "]";
+ switch (err_ctx) {
+ case ERRCTX_ERRNO:
+ tmp << " " << strerror(err);
+ break;
+#if WIN32
+ case ERRCTX_HRESULT: {
+ char msgbuf[256];
+ DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM;
+ HMODULE hmod = GetModuleHandleA(module);
+ if (hmod)
+ flags |= FORMAT_MESSAGE_FROM_HMODULE;
+ if (DWORD len = FormatMessageA(
+ flags, hmod, err,
+ MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+ msgbuf, sizeof(msgbuf) / sizeof(msgbuf[0]), NULL)) {
+ while ((len > 0) &&
+ isspace(static_cast<unsigned char>(msgbuf[len-1]))) {
+ msgbuf[--len] = 0;
+ }
+ tmp << " " << msgbuf;
+ }
+ break;
+ }
+#endif // WIN32
+#if OSX
+ case ERRCTX_OSSTATUS: {
+ tmp << " " << nonnull(GetMacOSStatusErrorString(err), "Unknown error");
+ if (const char* desc = GetMacOSStatusCommentString(err)) {
+ tmp << ": " << desc;
+ }
+ break;
+ }
+#endif // OSX
+ default:
+ break;
+ }
+ extra_ = tmp.str();
+ }
+}
+
+LogMessage::~LogMessage() {
+ if (!extra_.empty())
+ print_stream_ << " : " << extra_;
+ print_stream_ << std::endl;
+
+ const std::string& str = print_stream_.str();
+ if (severity_ >= dbg_sev_) {
+ OutputToDebug(str, severity_);
+ }
+
+ // Must lock streams_ before accessing
+ CritScope cs(&crit_);
+ for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ if (severity_ >= it->second) {
+ OutputToStream(it->first, str);
+ }
+ }
+}
+
+void LogMessage::LogContext(int min_sev) {
+ ctx_sev_ = min_sev;
+}
+
+void LogMessage::LogThreads(bool on) {
+ thread_ = on;
+}
+
+void LogMessage::LogTimestamps(bool on) {
+ timestamp_ = on;
+}
+
+void LogMessage::ResetTimestamps() {
+ start_ = Time();
+}
+
+void LogMessage::LogToDebug(int min_sev) {
+ dbg_sev_ = min_sev;
+ UpdateMinLogSeverity();
+}
+
+void LogMessage::LogToStream(StreamInterface* stream, int min_sev) {
+ CritScope cs(&crit_);
+ // Discard and delete all previously installed streams
+ for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ delete it->first;
+ }
+ streams_.clear();
+ // Install the new stream, if specified
+ if (stream) {
+ AddLogToStream(stream, min_sev);
+ }
+}
+
+int LogMessage::GetLogToStream(StreamInterface* stream) {
+ CritScope cs(&crit_);
+ int sev = NO_LOGGING;
+ for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ if (!stream || stream == it->first) {
+ sev = _min(sev, it->second);
+ }
+ }
+ return sev;
+}
+
+void LogMessage::AddLogToStream(StreamInterface* stream, int min_sev) {
+ CritScope cs(&crit_);
+ streams_.push_back(std::make_pair(stream, min_sev));
+ UpdateMinLogSeverity();
+}
+
+void LogMessage::RemoveLogToStream(StreamInterface* stream) {
+ CritScope cs(&crit_);
+ for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ if (stream == it->first) {
+ streams_.erase(it);
+ break;
+ }
+ }
+ UpdateMinLogSeverity();
+}
+
+void LogMessage::ConfigureLogging(const char* params, const char* filename) {
+ int current_level = LS_VERBOSE;
+ int debug_level = GetLogToDebug();
+ int file_level = GetLogToStream();
+
+ std::vector<std::string> tokens;
+ tokenize(params, ' ', &tokens);
+
+ for (size_t i = 0; i < tokens.size(); ++i) {
+ if (tokens[i].empty())
+ continue;
+
+ // Logging features
+ if (tokens[i] == "tstamp") {
+ LogTimestamps();
+ } else if (tokens[i] == "thread") {
+ LogThreads();
+
+ // Logging levels
+ } else if (tokens[i] == "sensitive") {
+ current_level = LS_SENSITIVE;
+ } else if (tokens[i] == "verbose") {
+ current_level = LS_VERBOSE;
+ } else if (tokens[i] == "info") {
+ current_level = LS_INFO;
+ } else if (tokens[i] == "warning") {
+ current_level = LS_WARNING;
+ } else if (tokens[i] == "error") {
+ current_level = LS_ERROR;
+ } else if (tokens[i] == "none") {
+ current_level = NO_LOGGING;
+
+ // Logging targets
+ } else if (tokens[i] == "file") {
+ file_level = current_level;
+ } else if (tokens[i] == "debug") {
+ debug_level = current_level;
+ }
+ }
+
+#ifdef WIN32
+ if ((NO_LOGGING != debug_level) && !::IsDebuggerPresent()) {
+ // First, attempt to attach to our parent's console... so if you invoke
+ // from the command line, we'll see the output there. Otherwise, create
+ // our own console window.
+ // Note: These methods fail if a console already exists, which is fine.
+ bool success = false;
+ typedef BOOL (WINAPI* PFN_AttachConsole)(DWORD);
+ if (HINSTANCE kernel32 = ::LoadLibrary(L"kernel32.dll")) {
+ // AttachConsole is defined on WinXP+.
+ if (PFN_AttachConsole attach_console = reinterpret_cast<PFN_AttachConsole>
+ (::GetProcAddress(kernel32, "AttachConsole"))) {
+ success = (FALSE != attach_console(ATTACH_PARENT_PROCESS));
+ }
+ ::FreeLibrary(kernel32);
+ }
+ if (!success) {
+ ::AllocConsole();
+ }
+ }
+#endif // WIN32
+
+ scoped_ptr<FileStream> stream;
+ if (NO_LOGGING != file_level) {
+ stream.reset(new FileStream);
+ if (!stream->Open(filename, "wb") || !stream->DisableBuffering()) {
+ stream.reset();
+ }
+ }
+
+ LogToDebug(debug_level);
+ LogToStream(stream.release(), file_level);
+}
+
+int LogMessage::ParseLogSeverity(const std::string& value) {
+ int level = NO_LOGGING;
+ if (value == "LS_SENSITIVE") {
+ level = LS_SENSITIVE;
+ } else if (value == "LS_VERBOSE") {
+ level = LS_VERBOSE;
+ } else if (value == "LS_INFO") {
+ level = LS_INFO;
+ } else if (value == "LS_WARNING") {
+ level = LS_WARNING;
+ } else if (value == "LS_ERROR") {
+ level = LS_ERROR;
+ } else if (isdigit(value[0])) {
+ level = atoi(value.c_str()); // NOLINT
+ }
+ return level;
+}
+
+void LogMessage::UpdateMinLogSeverity() {
+ int min_sev = dbg_sev_;
+ for (StreamList::iterator it = streams_.begin(); it != streams_.end(); ++it) {
+ min_sev = _min(dbg_sev_, it->second);
+ }
+ min_sev_ = min_sev;
+}
+
+const char* LogMessage::Describe(LoggingSeverity sev) {
+ switch (sev) {
+ case LS_SENSITIVE: return "Sensitive";
+ case LS_VERBOSE: return "Verbose";
+ case LS_INFO: return "Info";
+ case LS_WARNING: return "Warning";
+ case LS_ERROR: return "Error";
+ default: return "<unknown>";
+ }
+}
+
+const char* LogMessage::DescribeFile(const char* file) {
+ const char* end1 = ::strrchr(file, '/');
+ const char* end2 = ::strrchr(file, '\\');
+ if (!end1 && !end2)
+ return file;
+ else
+ return (end1 > end2) ? end1 + 1 : end2 + 1;
+}
+
+void LogMessage::OutputToDebug(const std::string& str,
+ LoggingSeverity severity) {
+ bool log_to_stderr = true;
+#if defined(OSX) && (!defined(DEBUG) || defined(NDEBUG))
+ // On the Mac, all stderr output goes to the Console log and causes clutter.
+ // So in opt builds, don't log to stderr unless the user specifically sets
+ // a preference to do so.
+ CFStringRef key = CFStringCreateWithCString(kCFAllocatorDefault,
+ "logToStdErr",
+ kCFStringEncodingUTF8);
+ CFStringRef domain = CFBundleGetIdentifier(CFBundleGetMainBundle());
+ if (key != NULL && domain != NULL) {
+ Boolean exists_and_is_valid;
+ Boolean should_log =
+ CFPreferencesGetAppBooleanValue(key, domain, &exists_and_is_valid);
+ // If the key doesn't exist or is invalid or is false, we will not log to
+ // stderr.
+ log_to_stderr = exists_and_is_valid && should_log;
+ }
+ if (key != NULL) {
+ CFRelease(key);
+ }
+#endif
+#ifdef WIN32
+ // Always log to the debugger.
+ // Perhaps stderr should be controlled by a preference, as on Mac?
+ OutputDebugStringA(str.c_str());
+ if (log_to_stderr) {
+ // This handles dynamically allocated consoles, too.
+ if (HANDLE error_handle = ::GetStdHandle(STD_ERROR_HANDLE)) {
+ log_to_stderr = false;
+ unsigned long written; // NOLINT
+ ::WriteFile(error_handle, str.data(), str.size(), &written, 0);
+ }
+ }
+#endif // WIN32
+#ifdef ANDROID
+ // Android's logging facility uses severity to log messages but we
+ // need to map libjingle's severity levels to Android ones first.
+ // Also write to stderr which maybe available to executable started
+ // from the shell.
+ int prio;
+ switch (severity) {
+ case LS_SENSITIVE:
+ __android_log_write(ANDROID_LOG_INFO, kLibjingle, "SENSITIVE");
+ if (log_to_stderr) {
+ std::cerr << "SENSITIVE";
+ std::cerr.flush();
+ }
+ return;
+ case LS_VERBOSE:
+ prio = ANDROID_LOG_VERBOSE;
+ break;
+ case LS_INFO:
+ prio = ANDROID_LOG_INFO;
+ break;
+ case LS_WARNING:
+ prio = ANDROID_LOG_WARN;
+ break;
+ case LS_ERROR:
+ prio = ANDROID_LOG_ERROR;
+ break;
+ default:
+ prio = ANDROID_LOG_UNKNOWN;
+ }
+
+ int size = str.size();
+ int line = 0;
+ int idx = 0;
+ const int max_lines = size / kMaxLogLineSize + 1;
+ if (max_lines == 1) {
+ __android_log_print(prio, kLibjingle, "%.*s", size, str.c_str());
+ } else {
+ while (size > 0) {
+ const int len = std::min(size, kMaxLogLineSize);
+ // Use the size of the string in the format (str may have \0 in the
+ // middle).
+ __android_log_print(prio, kLibjingle, "[%d/%d] %.*s",
+ line + 1, max_lines,
+ len, str.c_str() + idx);
+ idx += len;
+ size -= len;
+ ++line;
+ }
+ }
+#endif // ANDROID
+ if (log_to_stderr) {
+ std::cerr << str;
+ std::cerr.flush();
+ }
+}
+
+void LogMessage::OutputToStream(StreamInterface* stream,
+ const std::string& str) {
+ // If write isn't fully successful, what are we going to do, log it? :)
+ stream->WriteAll(str.data(), str.size(), NULL, NULL);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+ const void* data, size_t len, bool hex_mode,
+ LogMultilineState* state) {
+ if (!LOG_CHECK_LEVEL_V(level))
+ return;
+
+ const char * direction = (input ? " << " : " >> ");
+
+ // NULL data means to flush our count of unprintable characters.
+ if (!data) {
+ if (state && state->unprintable_count_[input]) {
+ LOG_V(level) << label << direction << "## "
+ << state->unprintable_count_[input]
+ << " consecutive unprintable ##";
+ state->unprintable_count_[input] = 0;
+ }
+ return;
+ }
+
+ // The ctype classification functions want unsigned chars.
+ const unsigned char* udata = static_cast<const unsigned char*>(data);
+
+ if (hex_mode) {
+ const size_t LINE_SIZE = 24;
+ char hex_line[LINE_SIZE * 9 / 4 + 2], asc_line[LINE_SIZE + 1];
+ while (len > 0) {
+ memset(asc_line, ' ', sizeof(asc_line));
+ memset(hex_line, ' ', sizeof(hex_line));
+ size_t line_len = _min(len, LINE_SIZE);
+ for (size_t i = 0; i < line_len; ++i) {
+ unsigned char ch = udata[i];
+ asc_line[i] = isprint(ch) ? ch : '.';
+ hex_line[i*2 + i/4] = hex_encode(ch >> 4);
+ hex_line[i*2 + i/4 + 1] = hex_encode(ch & 0xf);
+ }
+ asc_line[sizeof(asc_line)-1] = 0;
+ hex_line[sizeof(hex_line)-1] = 0;
+ LOG_V(level) << label << direction
+ << asc_line << " " << hex_line << " ";
+ udata += line_len;
+ len -= line_len;
+ }
+ return;
+ }
+
+ size_t consecutive_unprintable = state ? state->unprintable_count_[input] : 0;
+
+ const unsigned char* end = udata + len;
+ while (udata < end) {
+ const unsigned char* line = udata;
+ const unsigned char* end_of_line = strchrn<unsigned char>(udata,
+ end - udata,
+ '\n');
+ if (!end_of_line) {
+ udata = end_of_line = end;
+ } else {
+ udata = end_of_line + 1;
+ }
+
+ bool is_printable = true;
+
+ // If we are in unprintable mode, we need to see a line of at least
+ // kMinPrintableLine characters before we'll switch back.
+ const ptrdiff_t kMinPrintableLine = 4;
+ if (consecutive_unprintable && ((end_of_line - line) < kMinPrintableLine)) {
+ is_printable = false;
+ } else {
+ // Determine if the line contains only whitespace and printable
+ // characters.
+ bool is_entirely_whitespace = true;
+ for (const unsigned char* pos = line; pos < end_of_line; ++pos) {
+ if (isspace(*pos))
+ continue;
+ is_entirely_whitespace = false;
+ if (!isprint(*pos)) {
+ is_printable = false;
+ break;
+ }
+ }
+ // Treat an empty line following unprintable data as unprintable.
+ if (consecutive_unprintable && is_entirely_whitespace) {
+ is_printable = false;
+ }
+ }
+ if (!is_printable) {
+ consecutive_unprintable += (udata - line);
+ continue;
+ }
+ // Print out the current line, but prefix with a count of prior unprintable
+ // characters.
+ if (consecutive_unprintable) {
+ LOG_V(level) << label << direction << "## " << consecutive_unprintable
+ << " consecutive unprintable ##";
+ consecutive_unprintable = 0;
+ }
+ // Strip off trailing whitespace.
+ while ((end_of_line > line) && isspace(*(end_of_line-1))) {
+ --end_of_line;
+ }
+ // Filter out any private data
+ std::string substr(reinterpret_cast<const char*>(line), end_of_line - line);
+ std::string::size_type pos_private = substr.find("Email");
+ if (pos_private == std::string::npos) {
+ pos_private = substr.find("Passwd");
+ }
+ if (pos_private == std::string::npos) {
+ LOG_V(level) << label << direction << substr;
+ } else {
+ LOG_V(level) << label << direction << "## omitted for privacy ##";
+ }
+ }
+
+ if (state) {
+ state->unprintable_count_[input] = consecutive_unprintable;
+ }
+}
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/logging.h b/talk/base/logging.h
new file mode 100644
index 0000000..ac69b7b
--- /dev/null
+++ b/talk/base/logging.h
@@ -0,0 +1,381 @@
+/*
+ * 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.
+ */
+
+// LOG(...) an ostream target that can be used to send formatted
+// output to a variety of logging targets, such as debugger console, stderr,
+// file, or any StreamInterface.
+// The severity level passed as the first argument to the LOGging
+// functions is used as a filter, to limit the verbosity of the logging.
+// Static members of LogMessage documented below are used to control the
+// verbosity and target of the output.
+// There are several variations on the LOG macro which facilitate logging
+// of common error conditions, detailed below.
+
+// LOG(sev) logs the given stream at severity "sev", which must be a
+// compile-time constant of the LoggingSeverity type, without the namespace
+// prefix.
+// LOG_V(sev) Like LOG(), but sev is a run-time variable of the LoggingSeverity
+// type (basically, it just doesn't prepend the namespace).
+// LOG_F(sev) Like LOG(), but includes the name of the current function.
+// LOG_GLE(M)(sev [, mod]) attempt to add a string description of the
+// HRESULT returned by GetLastError. The "M" variant allows searching of a
+// DLL's string table for the error description.
+// LOG_ERRNO(sev) attempts to add a string description of an errno-derived
+// error. errno and associated facilities exist on both Windows and POSIX,
+// but on Windows they only apply to the C/C++ runtime.
+// LOG_ERR(sev) is an alias for the platform's normal error system, i.e. _GLE on
+// Windows and _ERRNO on POSIX.
+// (The above three also all have _EX versions that let you specify the error
+// code, rather than using the last one.)
+// LOG_E(sev, ctx, err, ...) logs a detailed error interpreted using the
+// specified context.
+// LOG_CHECK_LEVEL(sev) (and LOG_CHECK_LEVEL_V(sev)) can be used as a test
+// before performing expensive or sensitive operations whose sole purpose is
+// to output logging data at the desired level.
+// Lastly, PLOG(sev, err) is an alias for LOG_ERR_EX.
+
+#ifndef TALK_BASE_LOGGING_H_
+#define TALK_BASE_LOGGING_H_
+
+#ifdef HAVE_CONFIG_H
+#include "config.h" // NOLINT
+#endif
+
+#include <list>
+#include <sstream>
+#include <string>
+#include <utility>
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+
+namespace talk_base {
+
+class StreamInterface;
+
+///////////////////////////////////////////////////////////////////////////////
+// ConstantLabel can be used to easily generate string names from constant
+// values. This can be useful for logging descriptive names of error messages.
+// Usage:
+// const ConstantLabel LIBRARY_ERRORS[] = {
+// KLABEL(SOME_ERROR),
+// KLABEL(SOME_OTHER_ERROR),
+// ...
+// LASTLABEL
+// }
+//
+// int err = LibraryFunc();
+// LOG(LS_ERROR) << "LibraryFunc returned: "
+// << ErrorName(err, LIBRARY_ERRORS);
+
+struct ConstantLabel { int value; const char * label; };
+#define KLABEL(x) { x, #x }
+#define TLABEL(x, y) { x, y }
+#define LASTLABEL { 0, 0 }
+
+const char * FindLabel(int value, const ConstantLabel entries[]);
+std::string ErrorName(int err, const ConstantLabel* err_table);
+
+//////////////////////////////////////////////////////////////////////
+
+// Note that the non-standard LoggingSeverity aliases exist because they are
+// still in broad use. The meanings of the levels are:
+// LS_SENSITIVE: Information which should only be logged with the consent
+// of the user, due to privacy concerns.
+// LS_VERBOSE: This level is for data which we do not want to appear in the
+// normal debug log, but should appear in diagnostic logs.
+// LS_INFO: Chatty level used in debugging for all sorts of things, the default
+// in debug builds.
+// LS_WARNING: Something that may warrant investigation.
+// LS_ERROR: Something that should not have occurred.
+enum LoggingSeverity { LS_SENSITIVE, LS_VERBOSE, LS_INFO, LS_WARNING, LS_ERROR,
+ INFO = LS_INFO,
+ WARNING = LS_WARNING,
+ LERROR = LS_ERROR };
+
+// LogErrorContext assists in interpreting the meaning of an error value.
+enum LogErrorContext {
+ ERRCTX_NONE,
+ ERRCTX_ERRNO, // System-local errno
+ ERRCTX_HRESULT, // Windows HRESULT
+ ERRCTX_OSSTATUS, // MacOS OSStatus
+
+ // Abbreviations for LOG_E macro
+ ERRCTX_EN = ERRCTX_ERRNO, // LOG_E(sev, EN, x)
+ ERRCTX_HR = ERRCTX_HRESULT, // LOG_E(sev, HR, x)
+ ERRCTX_OS = ERRCTX_OSSTATUS, // LOG_E(sev, OS, x)
+};
+
+class LogMessage {
+ public:
+ static const int NO_LOGGING;
+
+ LogMessage(const char* file, int line, LoggingSeverity sev,
+ LogErrorContext err_ctx = ERRCTX_NONE, int err = 0,
+ const char* module = NULL);
+ ~LogMessage();
+
+ static inline bool Loggable(LoggingSeverity sev) { return (sev >= min_sev_); }
+ std::ostream& stream() { return print_stream_; }
+
+ // These are attributes which apply to all logging channels
+ // LogContext: Display the file and line number of the message
+ static void LogContext(int min_sev);
+ // LogThreads: Display the thread identifier of the current thread
+ static void LogThreads(bool on = true);
+ // LogTimestamps: Display the elapsed time of the program
+ static void LogTimestamps(bool on = true);
+
+ // Timestamps begin with program execution, but can be reset with this
+ // function for measuring the duration of an activity, or to synchronize
+ // timestamps between multiple instances.
+ static void ResetTimestamps();
+
+ // These are the available logging channels
+ // Debug: Debug console on Windows, otherwise stderr
+ static void LogToDebug(int min_sev);
+ static int GetLogToDebug() { return dbg_sev_; }
+
+ // Stream: Any non-blocking stream interface. LogMessage takes ownership of
+ // the stream. Multiple streams may be specified by using AddLogToStream.
+ // LogToStream is retained for backwards compatibility; when invoked, it
+ // will discard any previously set streams and install the specified stream.
+ // GetLogToStream gets the severity for the specified stream, of if none
+ // is specified, the minimum stream severity.
+ // RemoveLogToStream removes the specified stream, without destroying it.
+ static void LogToStream(StreamInterface* stream, int min_sev);
+ static int GetLogToStream(StreamInterface* stream = NULL);
+ static void AddLogToStream(StreamInterface* stream, int min_sev);
+ static void RemoveLogToStream(StreamInterface* stream);
+
+ // Testing against MinLogSeverity allows code to avoid potentially expensive
+ // logging operations by pre-checking the logging level.
+ static int GetMinLogSeverity() { return min_sev_; }
+
+ static void SetDiagnosticMode(bool f) { is_diagnostic_mode_ = f; }
+ static bool IsDiagnosticMode() { return is_diagnostic_mode_; }
+
+ // Parses the provided parameter stream to configure the options above.
+ // Useful for configuring logging from the command line. If file logging
+ // is enabled, it is output to the specified filename.
+ static void ConfigureLogging(const char* params, const char* filename);
+
+ // Convert the string to a LS_ value; also accept numeric values.
+ static int ParseLogSeverity(const std::string& value);
+
+ private:
+ typedef std::list<std::pair<StreamInterface*, int> > StreamList;
+
+ // Updates min_sev_ appropriately when debug sinks change.
+ static void UpdateMinLogSeverity();
+
+ // These assist in formatting some parts of the debug output.
+ static const char* Describe(LoggingSeverity sev);
+ static const char* DescribeFile(const char* file);
+
+ // These write out the actual log messages.
+ static void OutputToDebug(const std::string& msg, LoggingSeverity severity_);
+ static void OutputToStream(StreamInterface* stream, const std::string& msg);
+
+ // The ostream that buffers the formatted message before output
+ std::ostringstream print_stream_;
+
+ // The severity level of this message
+ LoggingSeverity severity_;
+
+ // String data generated in the constructor, that should be appended to
+ // the message before output.
+ std::string extra_;
+
+ // Global lock for the logging subsystem
+ static CriticalSection crit_;
+
+ // dbg_sev_ is the thresholds for those output targets
+ // min_sev_ is the minimum (most verbose) of those levels, and is used
+ // as a short-circuit in the logging macros to identify messages that won't
+ // be logged.
+ // ctx_sev_ is the minimum level at which file context is displayed
+ static int min_sev_, dbg_sev_, ctx_sev_;
+
+ // The output streams and their associated severities
+ static StreamList streams_;
+
+ // Flags for formatting options
+ static bool thread_, timestamp_;
+
+ // The timestamp at which logging started.
+ static uint32 start_;
+
+ // are we in diagnostic mode (as defined by the app)?
+ static bool is_diagnostic_mode_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(LogMessage);
+};
+
+//////////////////////////////////////////////////////////////////////
+// Logging Helpers
+//////////////////////////////////////////////////////////////////////
+
+class LogMultilineState {
+ public:
+ size_t unprintable_count_[2];
+ LogMultilineState() {
+ unprintable_count_[0] = unprintable_count_[1] = 0;
+ }
+};
+
+// When possible, pass optional state variable to track various data across
+// multiple calls to LogMultiline. Otherwise, pass NULL.
+void LogMultiline(LoggingSeverity level, const char* label, bool input,
+ const void* data, size_t len, bool hex_mode,
+ LogMultilineState* state);
+
+//////////////////////////////////////////////////////////////////////
+// Macros which automatically disable logging when LOGGING == 0
+//////////////////////////////////////////////////////////////////////
+
+// If LOGGING is not explicitly defined, default to enabled in debug mode
+#if !defined(LOGGING)
+#if defined(_DEBUG) && !defined(NDEBUG)
+#define LOGGING 1
+#else
+#define LOGGING 0
+#endif
+#endif // !defined(LOGGING)
+
+#ifndef LOG
+#if LOGGING
+
+// The following non-obvious technique for implementation of a
+// conditional log stream was stolen from google3/base/logging.h.
+
+// This class is used to explicitly ignore values in the conditional
+// logging macros. This avoids compiler warnings like "value computed
+// is not used" and "statement has no effect".
+
+class LogMessageVoidify {
+ public:
+ LogMessageVoidify() { }
+ // This has to be an operator with a precedence lower than << but
+ // higher than ?:
+ void operator&(std::ostream&) { }
+};
+
+#define LOG_SEVERITY_PRECONDITION(sev) \
+ !(talk_base::LogMessage::Loggable(sev)) \
+ ? (void) 0 \
+ : talk_base::LogMessageVoidify() &
+
+#define LOG(sev) \
+ LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+ talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev).stream()
+
+// The _V version is for when a variable is passed in. It doesn't do the
+// namespace concatination.
+#define LOG_V(sev) \
+ LOG_SEVERITY_PRECONDITION(sev) \
+ talk_base::LogMessage(__FILE__, __LINE__, sev).stream()
+
+// The _F version prefixes the message with the current function name.
+#if defined(__GNUC__) && defined(_DEBUG)
+#define LOG_F(sev) LOG(sev) << __PRETTY_FUNCTION__ << ": "
+#else
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#endif
+
+#define LOG_CHECK_LEVEL(sev) \
+ talk_base::LogCheckLevel(talk_base::sev)
+#define LOG_CHECK_LEVEL_V(sev) \
+ talk_base::LogCheckLevel(sev)
+inline bool LogCheckLevel(LoggingSeverity sev) {
+ return (LogMessage::GetMinLogSeverity() <= sev);
+}
+
+#define LOG_E(sev, ctx, err, ...) \
+ LOG_SEVERITY_PRECONDITION(talk_base::sev) \
+ talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+ talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+ .stream()
+
+#else // !LOGGING
+
+// Hopefully, the compiler will optimize away some of this code.
+// Note: syntax of "1 ? (void)0 : LogMessage" was causing errors in g++,
+// converted to "while (false)"
+#define LOG(sev) \
+ while (false)talk_base:: LogMessage(NULL, 0, talk_base::sev).stream()
+#define LOG_V(sev) \
+ while (false) talk_base::LogMessage(NULL, 0, sev).stream()
+#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": "
+#define LOG_CHECK_LEVEL(sev) \
+ false
+#define LOG_CHECK_LEVEL_V(sev) \
+ false
+
+#define LOG_E(sev, ctx, err, ...) \
+ while (false) talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \
+ talk_base::ERRCTX_ ## ctx, err , ##__VA_ARGS__) \
+ .stream()
+
+#endif // !LOGGING
+
+#define LOG_ERRNO_EX(sev, err) \
+ LOG_E(sev, ERRNO, err)
+#define LOG_ERRNO(sev) \
+ LOG_ERRNO_EX(sev, errno)
+
+#ifdef WIN32
+#define LOG_GLE_EX(sev, err) \
+ LOG_E(sev, HRESULT, err)
+#define LOG_GLE(sev) \
+ LOG_GLE_EX(sev, GetLastError())
+#define LOG_GLEM(sev, mod) \
+ LOG_E(sev, HRESULT, GetLastError(), mod)
+#define LOG_ERR_EX(sev, err) \
+ LOG_GLE_EX(sev, err)
+#define LOG_ERR(sev) \
+ LOG_GLE(sev)
+#define LAST_SYSTEM_ERROR \
+ (::GetLastError())
+#elif POSIX
+#define LOG_ERR_EX(sev, err) \
+ LOG_ERRNO_EX(sev, err)
+#define LOG_ERR(sev) \
+ LOG_ERRNO(sev)
+#define LAST_SYSTEM_ERROR \
+ (errno)
+#endif // WIN32
+
+#define PLOG(sev, err) \
+ LOG_ERR_EX(sev, err)
+
+// TODO(?): Add an "assert" wrapper that logs in the same manner.
+
+#endif // LOG
+
+} // namespace talk_base
+
+#endif // TALK_BASE_LOGGING_H_
diff --git a/talk/base/macconversion.cc b/talk/base/macconversion.cc
new file mode 100644
index 0000000..4654e53
--- /dev/null
+++ b/talk/base/macconversion.cc
@@ -0,0 +1,176 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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 OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/macconversion.h"
+
+bool p_convertHostCFStringRefToCPPString(
+ const CFStringRef cfstr, std::string& cppstr) {
+ bool result = false;
+
+ // First this must be non-null,
+ if (NULL != cfstr) {
+ // it must actually *be* a CFString, and not something just masquerading
+ // as one,
+ if (CFGetTypeID(cfstr) == CFStringGetTypeID()) {
+ // and we must be able to get the characters out of it.
+ // (The cfstr owns this buffer; it came from somewhere else,
+ // so someone else gets to take care of getting rid of the cfstr,
+ // and then this buffer will go away automatically.)
+ unsigned length = CFStringGetLength(cfstr);
+ char* buf = new char[1 + length];
+ if (CFStringGetCString(cfstr, buf, 1 + length, kCFStringEncodingASCII)) {
+ if (strlen(buf) == length) {
+ cppstr.assign(buf);
+ result = true;
+ }
+ }
+ delete [] buf;
+ }
+ }
+
+ return result;
+}
+
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i) {
+ bool converted = false;
+
+ // It must not be null.
+ if (NULL != cfn) {
+ // It must actually *be* a CFNumber and not something just masquerading
+ // as one.
+ if (CFGetTypeID(cfn) == CFNumberGetTypeID()) {
+ CFNumberType ntype = CFNumberGetType(cfn);
+ switch (ntype) {
+ case kCFNumberSInt8Type:
+ SInt8 sint8;
+ converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint8));
+ if (converted) *i = static_cast<int>(sint8);
+ break;
+ case kCFNumberSInt16Type:
+ SInt16 sint16;
+ converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint16));
+ if (converted) *i = static_cast<int>(sint16);
+ break;
+ case kCFNumberSInt32Type:
+ SInt32 sint32;
+ converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint32));
+ if (converted) *i = static_cast<int>(sint32);
+ break;
+ case kCFNumberSInt64Type:
+ SInt64 sint64;
+ converted = CFNumberGetValue(cfn, ntype, static_cast<void*>(&sint64));
+ if (converted) *i = static_cast<int>(sint64);
+ break;
+ case kCFNumberFloat32Type:
+ Float32 float32;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&float32));
+ if (converted) *i = static_cast<int>(float32);
+ break;
+ case kCFNumberFloat64Type:
+ Float64 float64;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&float64));
+ if (converted) *i = static_cast<int>(float64);
+ break;
+ case kCFNumberCharType:
+ char charvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&charvalue));
+ if (converted) *i = static_cast<int>(charvalue);
+ break;
+ case kCFNumberShortType:
+ short shortvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&shortvalue));
+ if (converted) *i = static_cast<int>(shortvalue);
+ break;
+ case kCFNumberIntType:
+ int intvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&intvalue));
+ if (converted) *i = static_cast<int>(intvalue);
+ break;
+ case kCFNumberLongType:
+ long longvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&longvalue));
+ if (converted) *i = static_cast<int>(longvalue);
+ break;
+ case kCFNumberLongLongType:
+ long long llvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&llvalue));
+ if (converted) *i = static_cast<int>(llvalue);
+ break;
+ case kCFNumberFloatType:
+ float floatvalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&floatvalue));
+ if (converted) *i = static_cast<int>(floatvalue);
+ break;
+ case kCFNumberDoubleType:
+ double doublevalue;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&doublevalue));
+ if (converted) *i = static_cast<int>(doublevalue);
+ break;
+ case kCFNumberCFIndexType:
+ CFIndex cfindex;
+ converted = CFNumberGetValue(cfn, ntype,
+ static_cast<void*>(&cfindex));
+ if (converted) *i = static_cast<int>(cfindex);
+ break;
+ default:
+ LOG(LS_ERROR) << "got unknown type.";
+ break;
+ }
+ }
+ }
+
+ return converted;
+}
+
+bool p_isCFNumberTrue(CFNumberRef cfn) {
+ // We assume it's false until proven otherwise.
+ bool result = false;
+ int asInt;
+ bool converted = p_convertCFNumberToInt(cfn, &asInt);
+
+ if (converted && (0 != asInt)) {
+ result = true;
+ }
+
+ return result;
+}
+
+#endif // OSX
diff --git a/talk/base/macconversion.h b/talk/base/macconversion.h
new file mode 100644
index 0000000..a401cab
--- /dev/null
+++ b/talk/base/macconversion.h
@@ -0,0 +1,56 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_MACCONVERSION_H_
+#define TALK_BASE_MACCONVERSION_H_
+
+#ifdef OSX
+
+#include <CoreFoundation/CoreFoundation.h>
+
+#include <string>
+
+// given a CFStringRef, attempt to convert it to a C++ string.
+// returns true if it succeeds, false otherwise.
+// We can safely assume, given our context, that the string is
+// going to be in ASCII, because it will either be an IP address,
+// or a domain name, which is guaranteed to be ASCII-representable.
+bool p_convertHostCFStringRefToCPPString(const CFStringRef cfstr,
+ std::string& cppstr);
+
+// Convert the CFNumber to an integer, putting the integer in the location
+// given, and returhing true, if the conversion succeeds.
+// If given a NULL or a non-CFNumber, returns false.
+// This is pretty aggresive about trying to convert to int.
+bool p_convertCFNumberToInt(CFNumberRef cfn, int* i);
+
+// given a CFNumberRef, determine if it represents a true value.
+bool p_isCFNumberTrue(CFNumberRef cfn);
+
+#endif // OSX
+
+#endif // TALK_BASE_MACCONVERSION_H_
diff --git a/talk/base/macutils.cc b/talk/base/macutils.cc
new file mode 100644
index 0000000..fefe2b9
--- /dev/null
+++ b/talk/base/macutils.cc
@@ -0,0 +1,220 @@
+/*
+ * libjingle
+ * Copyright 2007--2009, 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 <sstream>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/macutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ToUtf8(const CFStringRef str16, std::string* str8) {
+ if ((NULL == str16) || (NULL == str8))
+ return false;
+ size_t maxlen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(str16),
+ kCFStringEncodingUTF8)
+ + 1;
+ scoped_array<char> buffer(new char[maxlen]);
+ if (!buffer.get()
+ || !CFStringGetCString(str16, buffer.get(), maxlen,
+ kCFStringEncodingUTF8))
+ return false;
+ str8->assign(buffer.get());
+ return true;
+}
+
+bool ToUtf16(const std::string& str8, CFStringRef* str16) {
+ if (NULL == str16)
+ return false;
+ *str16 = CFStringCreateWithBytes(kCFAllocatorDefault,
+ reinterpret_cast<const UInt8*>(str8.data()),
+ str8.length(), kCFStringEncodingUTF8,
+ false);
+ return (NULL != *str16);
+}
+
+void DecodeFourChar(UInt32 fc, std::string* out) {
+ std::stringstream ss;
+ ss << '\'';
+ bool printable = true;
+ for (int i = 3; i >= 0; --i) {
+ char ch = (fc >> (8 * i)) & 0xFF;
+ if (isprint(static_cast<unsigned char>(ch))) {
+ ss << ch;
+ } else {
+ printable = false;
+ break;
+ }
+ }
+ if (printable) {
+ ss << '\'';
+ } else {
+ ss.str("");
+ ss << "0x" << std::hex << fc;
+ }
+ out->append(ss.str());
+}
+
+std::string DecodeEvent(EventRef event) {
+ std::string str;
+ DecodeFourChar(::GetEventClass(event), &str);
+ str.push_back(':');
+ DecodeFourChar(::GetEventKind(event), &str);
+ return str;
+}
+
+static bool GetGestalt(OSType ostype, int* value) {
+ ASSERT(NULL != value);
+ SInt32 native_value;
+ OSStatus result = Gestalt(ostype, &native_value);
+ if (noErr == result) {
+ *value = native_value;
+ return true;
+ }
+ std::string str;
+ DecodeFourChar(ostype, &str);
+ LOG_E(LS_ERROR, OS, result) << "Gestalt(" << str << ")";
+ return false;
+}
+
+bool GetOSVersion(int* major, int* minor, int* bugfix) {
+ ASSERT(major && minor && bugfix);
+ if (!GetGestalt(gestaltSystemVersion, major))
+ return false;
+ if (*major < 0x1040) {
+ *bugfix = *major & 0xF;
+ *minor = (*major >> 4) & 0xF;
+ *major = (*major >> 8);
+ return true;
+ }
+ return GetGestalt(gestaltSystemVersionMajor, major)
+ && GetGestalt(gestaltSystemVersionMinor, minor)
+ && GetGestalt(gestaltSystemVersionBugFix, bugfix);
+}
+
+MacOSVersionName GetOSVersionName() {
+ int major = 0, minor = 0, bugfix = 0;
+ if (!GetOSVersion(&major, &minor, &bugfix))
+ return kMacOSUnknown;
+ if (major > 10)
+ return kMacOSNewer;
+ if ((major < 10) || (minor < 3))
+ return kMacOSOlder;
+ switch (minor) {
+ case 3:
+ return kMacOSPanther;
+ case 4:
+ return kMacOSTiger;
+ case 5:
+ return kMacOSLeopard;
+ }
+ return kMacOSNewer;
+}
+
+bool GetQuickTimeVersion(std::string* out) {
+ int ver;
+ if (!GetGestalt(gestaltQuickTimeVersion, &ver))
+ return false;
+
+ std::stringstream ss;
+ ss << std::hex << ver;
+ *out = ss.str();
+ return true;
+}
+
+bool RunAppleScript(const std::string& script) {
+ ComponentInstance component = NULL;
+ AEDesc script_desc;
+ AEDesc result_data;
+ OSStatus err;
+ OSAID script_id, result_id;
+
+ AECreateDesc(typeNull, NULL, 0, &script_desc);
+ AECreateDesc(typeNull, NULL, 0, &result_data);
+ script_id = kOSANullScript;
+ result_id = kOSANullScript;
+
+ component = OpenDefaultComponent(kOSAComponentType, typeAppleScript);
+ if (component == NULL) {
+ LOG(LS_ERROR) << "Failed opening Apple Script component";
+ return false;
+ }
+ err = AECreateDesc(typeUTF8Text, script.data(), script.size(), &script_desc);
+ if (err != noErr) {
+ CloseComponent(component);
+ LOG(LS_ERROR) << "Failed creating Apple Script description";
+ return false;
+ }
+
+ err = OSACompile(component, &script_desc, kOSAModeCanInteract, &script_id);
+ if (err != noErr) {
+ AEDisposeDesc(&script_desc);
+ if (script_id != kOSANullScript) {
+ OSADispose(component, script_id);
+ }
+ CloseComponent(component);
+ LOG(LS_ERROR) << "Error compiling Apple Script";
+ return false;
+ }
+
+ err = OSAExecute(component, script_id, kOSANullScript, kOSAModeCanInteract,
+ &result_id);
+
+ if (err == errOSAScriptError) {
+ LOG(LS_ERROR) << "Error when executing Apple Script: " << script;
+ AECreateDesc(typeNull, NULL, 0, &result_data);
+ OSAScriptError(component, kOSAErrorMessage, typeChar, &result_data);
+ int len = AEGetDescDataSize(&result_data);
+ char* data = (char*) malloc(len);
+ if (data != NULL) {
+ err = AEGetDescData(&result_data, data, len);
+ LOG(LS_ERROR) << "Script error: " << data;
+ }
+ AEDisposeDesc(&script_desc);
+ AEDisposeDesc(&result_data);
+ return false;
+ }
+ AEDisposeDesc(&script_desc);
+ if (script_id != kOSANullScript) {
+ OSADispose(component, script_id);
+ }
+ if (result_id != kOSANullScript) {
+ OSADispose(component, result_id);
+ }
+ CloseComponent(component);
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/macutils.h b/talk/base/macutils.h
new file mode 100644
index 0000000..30bb300
--- /dev/null
+++ b/talk/base/macutils.h
@@ -0,0 +1,66 @@
+/*
+ * libjingle
+ * Copyright 2007--2009, 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_MACUTILS_H__
+#define TALK_BASE_MACUTILS_H__
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Carbon/Carbon.h>
+#include <string>
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+bool ToUtf8(const CFStringRef str16, std::string* str8);
+bool ToUtf16(const std::string& str8, CFStringRef* str16);
+
+void DecodeFourChar(UInt32 fc, std::string* out);
+std::string DecodeEvent(EventRef event);
+
+enum MacOSVersionName {
+ kMacOSUnknown, // ???
+ kMacOSOlder, // 10.2-
+ kMacOSPanther, // 10.3
+ kMacOSTiger, // 10.4
+ kMacOSLeopard, // 10.5
+ kMacOSNewer, // 10.6+
+};
+
+bool GetOSVersion(int* major, int* minor, int* bugfix);
+MacOSVersionName GetOSVersionName();
+bool GetQuickTimeVersion(std::string* version);
+
+// Runs the given apple script. Only supports scripts that does not
+// require user interaction.
+bool RunAppleScript(const std::string& script);
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MACUTILS_H__
diff --git a/talk/base/md5.h b/talk/base/md5.h
new file mode 100644
index 0000000..ec458d1
--- /dev/null
+++ b/talk/base/md5.h
@@ -0,0 +1,45 @@
+/*
+ * This is the header file for the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ *
+ */
+
+#ifndef TALK_BASE_MD5_H__
+#define TALK_BASE_MD5_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef long unsigned int uint32;
+typedef struct MD5Context MD5_CTX;
+
+#define md5byte unsigned char
+
+struct MD5Context {
+ uint32 buf[4];
+ uint32 bits[2];
+ uint32 in[16];
+};
+
+void MD5Init(struct MD5Context *context);
+void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len);
+void MD5Final(unsigned char digest[16], struct MD5Context *context);
+void MD5Transform(uint32 buf[4], uint32 const in[16]);
+
+#ifdef __cplusplus
+};
+#endif
+
+#endif // TALK_BASE_MD5_H__
diff --git a/talk/base/md5c.c b/talk/base/md5c.c
new file mode 100644
index 0000000..eb2c034
--- /dev/null
+++ b/talk/base/md5c.c
@@ -0,0 +1,256 @@
+/*
+ * This code implements the MD5 message-digest algorithm.
+ * The algorithm is due to Ron Rivest. This code was
+ * written by Colin Plumb in 1993, no copyright is claimed.
+ * This code is in the public domain; do with it what you wish.
+ *
+ * Equivalent code is available from RSA Data Security, Inc.
+ * This code has been tested against that, and is equivalent,
+ * except that you don't need to include two pages of legalese
+ * with every copy.
+ *
+ * To compute the message digest of a chunk of bytes, declare an
+ * MD5Context structure, pass it to MD5Init, call MD5Update as
+ * needed on buffers full of bytes, and then call MD5Final, which
+ * will fill a supplied 16-byte array with the digest.
+ */
+#include <string.h> /* for memcpy() */
+#include "md5.h"
+
+#ifndef HIGHFIRST
+#define byteReverse(buf, len) /* Nothing */
+#else
+void byteReverse(unsigned char *buf, unsigned longs);
+
+#ifndef ASM_MD5
+/*
+ * Note: this code is harmless on little-endian machines.
+ */
+void byteReverse(unsigned char *buf, unsigned longs)
+{
+ uint32 t;
+ do {
+ t = (uint32)((unsigned)buf[3]<<8 | buf[2]) << 16 |
+ ((unsigned)buf[1]<<8 | buf[0]);
+ *(uint32 *)buf = t;
+ buf += 4;
+ } while (--longs);
+}
+#endif
+#endif
+
+/*
+ * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
+ * initialization constants.
+ */
+void
+MD5Init(struct MD5Context *ctx)
+{
+ ctx->buf[0] = 0x67452301;
+ ctx->buf[1] = 0xefcdab89;
+ ctx->buf[2] = 0x98badcfe;
+ ctx->buf[3] = 0x10325476;
+
+ ctx->bits[0] = 0;
+ ctx->bits[1] = 0;
+}
+
+/*
+ * Update context to reflect the concatenation of another buffer full
+ * of bytes.
+ */
+void
+MD5Update(struct MD5Context *ctx, unsigned char const *buf, unsigned len)
+{
+ uint32 t;
+
+ /* Update bitcount */
+
+ t = ctx->bits[0];
+ if ((ctx->bits[0] = t + ((uint32)len << 3)) < t)
+ ctx->bits[1]++; /* Carry from low to high */
+ ctx->bits[1] += len >> 29;
+
+ t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
+
+ /* Handle any leading odd-sized chunks */
+
+ if ( t ) {
+ unsigned char *p = (unsigned char *)ctx->in + t;
+
+ t = 64-t;
+ if (len < t) {
+ memcpy(p, buf, len);
+ return;
+ }
+ memcpy(p, buf, t);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += t;
+ len -= t;
+ }
+
+ /* Process data in 64-byte chunks */
+
+ while (len >= 64) {
+ memcpy(ctx->in, buf, 64);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ buf += 64;
+ len -= 64;
+ }
+
+ /* Handle any remaining bytes of data. */
+
+ memcpy(ctx->in, buf, len);
+}
+
+/*
+ * Final wrapup - pad to 64-byte boundary with the bit pattern
+ * 1 0* (64-bit count of bits processed, MSB-first)
+ */
+void
+MD5Final(unsigned char digest[16], struct MD5Context *ctx)
+{
+ unsigned count;
+ unsigned char *p;
+
+ /* Compute number of bytes mod 64 */
+ count = (ctx->bits[0] >> 3) & 0x3F;
+
+ /* Set the first char of padding to 0x80. This is safe since there is
+ always at least one byte free */
+ p = (unsigned char*)(ctx->in) + count;
+ *p++ = 0x80;
+
+ /* Bytes of padding needed to make 64 bytes */
+ count = 64 - 1 - count;
+
+ /* Pad out to 56 mod 64 */
+ if (count < 8) {
+ /* Two lots of padding: Pad the first block to 64 bytes */
+ memset(p, 0, count);
+ byteReverse(ctx->in, 16);
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+
+ /* Now fill the next block with 56 bytes */
+ memset(ctx->in, 0, 56);
+ } else {
+ /* Pad block to 56 bytes */
+ memset(p, 0, count-8);
+ }
+ byteReverse(ctx->in, 14);
+
+ /* Append length in bits and transform */
+ ((uint32 *)ctx->in)[ 14 ] = ctx->bits[0];
+ ((uint32 *)ctx->in)[ 15 ] = ctx->bits[1];
+
+ MD5Transform(ctx->buf, (uint32 *)ctx->in);
+ byteReverse((unsigned char *)ctx->buf, 4);
+ memcpy(digest, ctx->buf, 16);
+ memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
+}
+
+#ifndef ASM_MD5
+
+/* The four core functions - F1 is optimized somewhat */
+
+/* #define F1(x, y, z) (x & y | ~x & z) */
+#define F1(x, y, z) (z ^ (x & (y ^ z)))
+#define F2(x, y, z) F1(z, x, y)
+#define F3(x, y, z) (x ^ y ^ z)
+#define F4(x, y, z) (y ^ (x | ~z))
+
+/* This is the central step in the MD5 algorithm. */
+#define MD5STEP(f, w, x, y, z, data, s) \
+ ( w += f(x, y, z) + data, w = w<<s | w>>(32-s), w += x )
+
+/*
+ * The core of the MD5 algorithm, this alters an existing MD5 hash to
+ * reflect the addition of 16 longwords of new data. MD5Update blocks
+ * the data and converts bytes into longwords for this routine.
+ */
+void
+MD5Transform(uint32 buf[4], uint32 const in[16])
+{
+ register uint32 a, b, c, d;
+
+ a = buf[0];
+ b = buf[1];
+ c = buf[2];
+ d = buf[3];
+
+ MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
+ MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
+ MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
+ MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
+ MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
+ MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
+ MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
+ MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
+ MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
+ MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
+ MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
+ MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
+ MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
+ MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
+ MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
+ MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
+
+ MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
+ MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
+ MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
+ MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
+ MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
+ MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
+ MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
+ MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
+ MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
+ MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
+ MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
+ MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
+ MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
+ MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
+ MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
+ MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
+
+ MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
+ MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
+ MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
+ MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
+ MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
+ MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
+ MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
+ MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
+ MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
+ MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
+ MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
+ MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
+ MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
+ MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
+ MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
+ MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
+
+ MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
+ MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
+ MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
+ MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
+ MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
+ MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
+ MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
+ MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
+ MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
+ MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
+ MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
+ MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
+ MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
+ MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
+ MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
+
+ buf[0] += a;
+ buf[1] += b;
+ buf[2] += c;
+ buf[3] += d;
+}
+#endif
diff --git a/talk/base/messagehandler.cc b/talk/base/messagehandler.cc
new file mode 100644
index 0000000..5b3585b
--- /dev/null
+++ b/talk/base/messagehandler.cc
@@ -0,0 +1,37 @@
+/*
+ * 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 "talk/base/messagehandler.h"
+#include "talk/base/messagequeue.h"
+
+namespace talk_base {
+
+MessageHandler::~MessageHandler() {
+ MessageQueueManager::Instance()->Clear(this);
+}
+
+} // namespace talk_base
diff --git a/talk/base/messagehandler.h b/talk/base/messagehandler.h
new file mode 100644
index 0000000..eb7db9b
--- /dev/null
+++ b/talk/base/messagehandler.h
@@ -0,0 +1,46 @@
+/*
+ * 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_MESSAGEHANDLER_H__
+#define TALK_BASE_MESSAGEHANDLER_H__
+
+namespace talk_base {
+
+struct Message;
+
+// Messages get dispatched to a MessageHandler
+
+class MessageHandler {
+public:
+ virtual ~MessageHandler();
+
+ virtual void OnMessage(Message* msg) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MESSAGEHANDLER_H__
diff --git a/talk/base/messagequeue.cc b/talk/base/messagequeue.cc
new file mode 100644
index 0000000..8c3a8ce
--- /dev/null
+++ b/talk/base/messagequeue.cc
@@ -0,0 +1,383 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/physicalsocketserver.h"
+
+
+namespace talk_base {
+
+const uint32 kMaxMsgLatency = 150; // 150 ms
+
+//------------------------------------------------------------------
+// MessageQueueManager
+
+MessageQueueManager* MessageQueueManager::instance_;
+
+MessageQueueManager* MessageQueueManager::Instance() {
+ // Note: This is not thread safe, but it is first called before threads are
+ // spawned.
+ if (!instance_)
+ instance_ = new MessageQueueManager;
+ return instance_;
+}
+
+MessageQueueManager::MessageQueueManager() {
+}
+
+MessageQueueManager::~MessageQueueManager() {
+}
+
+void MessageQueueManager::Add(MessageQueue *message_queue) {
+ // MessageQueueManager methods should be non-reentrant, so we
+ // ASSERT that is the case. If any of these ASSERT, please
+ // contact bpm or jbeda.
+ ASSERT(!crit_.CurrentThreadIsOwner());
+ CritScope cs(&crit_);
+ message_queues_.push_back(message_queue);
+}
+
+void MessageQueueManager::Remove(MessageQueue *message_queue) {
+ ASSERT(!crit_.CurrentThreadIsOwner()); // See note above.
+ // If this is the last MessageQueue, destroy the manager as well so that
+ // we don't leak this object at program shutdown. As mentioned above, this is
+ // not thread-safe, but this should only happen at program termination (when
+ // the ThreadManager is destroyed, and threads are no longer active).
+ bool destroy = false;
+ {
+ CritScope cs(&crit_);
+ std::vector<MessageQueue *>::iterator iter;
+ iter = std::find(message_queues_.begin(), message_queues_.end(),
+ message_queue);
+ if (iter != message_queues_.end()) {
+ message_queues_.erase(iter);
+ }
+ destroy = message_queues_.empty();
+ }
+ if (destroy) {
+ instance_ = NULL;
+ delete this;
+ }
+}
+
+void MessageQueueManager::Clear(MessageHandler *handler) {
+ ASSERT(!crit_.CurrentThreadIsOwner()); // See note above.
+ CritScope cs(&crit_);
+ std::vector<MessageQueue *>::iterator iter;
+ for (iter = message_queues_.begin(); iter != message_queues_.end(); iter++)
+ (*iter)->Clear(handler);
+}
+
+//------------------------------------------------------------------
+// MessageQueue
+
+MessageQueue::MessageQueue(SocketServer* ss)
+ : ss_(ss), fStop_(false), fPeekKeep_(false), active_(false),
+ dmsgq_next_num_(0) {
+ if (!ss_) {
+ // Currently, MessageQueue holds a socket server, and is the base class for
+ // Thread. It seems like it makes more sense for Thread to hold the socket
+ // server, and provide it to the MessageQueue, since the Thread controls
+ // the I/O model, and MQ is agnostic to those details. Anyway, this causes
+ // messagequeue_unittest to depend on network libraries... yuck.
+ default_ss_.reset(new PhysicalSocketServer());
+ ss_ = default_ss_.get();
+ }
+ ss_->SetMessageQueue(this);
+}
+
+MessageQueue::~MessageQueue() {
+ // The signal is done from here to ensure
+ // that it always gets called when the queue
+ // is going away.
+ SignalQueueDestroyed();
+ if (active_) {
+ MessageQueueManager::Instance()->Remove(this);
+ Clear(NULL);
+ }
+ if (ss_) {
+ ss_->SetMessageQueue(NULL);
+ }
+}
+
+void MessageQueue::set_socketserver(SocketServer* ss) {
+ ss_ = ss ? ss : default_ss_.get();
+ ss_->SetMessageQueue(this);
+}
+
+void MessageQueue::Quit() {
+ fStop_ = true;
+ ss_->WakeUp();
+}
+
+bool MessageQueue::IsQuitting() {
+ return fStop_;
+}
+
+void MessageQueue::Restart() {
+ fStop_ = false;
+}
+
+bool MessageQueue::Peek(Message *pmsg, int cmsWait) {
+ if (fPeekKeep_) {
+ *pmsg = msgPeek_;
+ return true;
+ }
+ if (!Get(pmsg, cmsWait))
+ return false;
+ msgPeek_ = *pmsg;
+ fPeekKeep_ = true;
+ return true;
+}
+
+bool MessageQueue::Get(Message *pmsg, int cmsWait, bool process_io) {
+ // Return and clear peek if present
+ // Always return the peek if it exists so there is Peek/Get symmetry
+
+ if (fPeekKeep_) {
+ *pmsg = msgPeek_;
+ fPeekKeep_ = false;
+ return true;
+ }
+
+ // Get w/wait + timer scan / dispatch + socket / event multiplexer dispatch
+
+ int cmsTotal = cmsWait;
+ int cmsElapsed = 0;
+ uint32 msStart = Time();
+ uint32 msCurrent = msStart;
+ while (true) {
+ // Check for sent messages
+
+ ReceiveSends();
+
+ // Check queues
+
+ int cmsDelayNext = kForever;
+ {
+ CritScope cs(&crit_);
+
+ // Check for delayed messages that have been triggered
+ // Calc the next trigger too
+
+ while (!dmsgq_.empty()) {
+ if (TimeIsLater(msCurrent, dmsgq_.top().msTrigger_)) {
+ cmsDelayNext = TimeDiff(dmsgq_.top().msTrigger_, msCurrent);
+ break;
+ }
+ msgq_.push_back(dmsgq_.top().msg_);
+ dmsgq_.pop();
+ }
+
+ // Check for posted events
+
+ while (!msgq_.empty()) {
+ *pmsg = msgq_.front();
+ if (pmsg->ts_sensitive) {
+ long delay = TimeDiff(msCurrent, pmsg->ts_sensitive);
+ if (delay > 0) {
+ LOG_F(LS_WARNING) << "id: " << pmsg->message_id << " delay: "
+ << (delay + kMaxMsgLatency) << "ms";
+ }
+ }
+ msgq_.pop_front();
+ if (MQID_DISPOSE == pmsg->message_id) {
+ ASSERT(NULL == pmsg->phandler);
+ delete pmsg->pdata;
+ continue;
+ }
+ return true;
+ }
+ }
+
+ if (fStop_)
+ break;
+
+ // Which is shorter, the delay wait or the asked wait?
+
+ int cmsNext;
+ if (cmsWait == kForever) {
+ cmsNext = cmsDelayNext;
+ } else {
+ cmsNext = _max(0, cmsTotal - cmsElapsed);
+ if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext))
+ cmsNext = cmsDelayNext;
+ }
+
+ // Wait and multiplex in the meantime
+ if (!ss_->Wait(cmsNext, process_io))
+ return false;
+
+ // If the specified timeout expired, return
+
+ msCurrent = Time();
+ cmsElapsed = TimeDiff(msCurrent, msStart);
+ if (cmsWait != kForever) {
+ if (cmsElapsed >= cmsWait)
+ return false;
+ }
+ }
+ return false;
+}
+
+void MessageQueue::ReceiveSends() {
+}
+
+void MessageQueue::Post(MessageHandler *phandler, uint32 id,
+ MessageData *pdata, bool time_sensitive) {
+ if (fStop_)
+ return;
+
+ // Keep thread safe
+ // Add the message to the end of the queue
+ // Signal for the multiplexer to return
+
+ CritScope cs(&crit_);
+ EnsureActive();
+ Message msg;
+ msg.phandler = phandler;
+ msg.message_id = id;
+ msg.pdata = pdata;
+ if (time_sensitive) {
+ msg.ts_sensitive = Time() + kMaxMsgLatency;
+ }
+ msgq_.push_back(msg);
+ ss_->WakeUp();
+}
+
+void MessageQueue::DoDelayPost(int cmsDelay, uint32 tstamp,
+ MessageHandler *phandler, uint32 id, MessageData* pdata) {
+ if (fStop_)
+ return;
+
+ // Keep thread safe
+ // Add to the priority queue. Gets sorted soonest first.
+ // Signal for the multiplexer to return.
+
+ CritScope cs(&crit_);
+ EnsureActive();
+ Message msg;
+ msg.phandler = phandler;
+ msg.message_id = id;
+ msg.pdata = pdata;
+ DelayedMessage dmsg(cmsDelay, tstamp, dmsgq_next_num_, msg);
+ dmsgq_.push(dmsg);
+ // If this message queue processes 1 message every millisecond for 50 days,
+ // we will wrap this number. Even then, only messages with identical times
+ // will be misordered, and then only briefly. This is probably ok.
+ VERIFY(0 != ++dmsgq_next_num_);
+ ss_->WakeUp();
+}
+
+int MessageQueue::GetDelay() {
+ CritScope cs(&crit_);
+
+ if (!msgq_.empty())
+ return 0;
+
+ if (!dmsgq_.empty()) {
+ int delay = TimeUntil(dmsgq_.top().msTrigger_);
+ if (delay < 0)
+ delay = 0;
+ return delay;
+ }
+
+ return kForever;
+}
+
+void MessageQueue::Clear(MessageHandler *phandler, uint32 id,
+ MessageList* removed) {
+ CritScope cs(&crit_);
+
+ // Remove messages with phandler
+
+ if (fPeekKeep_ && msgPeek_.Match(phandler, id)) {
+ if (removed) {
+ removed->push_back(msgPeek_);
+ } else {
+ delete msgPeek_.pdata;
+ }
+ fPeekKeep_ = false;
+ }
+
+ // Remove from ordered message queue
+
+ for (MessageList::iterator it = msgq_.begin(); it != msgq_.end();) {
+ if (it->Match(phandler, id)) {
+ if (removed) {
+ removed->push_back(*it);
+ } else {
+ delete it->pdata;
+ }
+ it = msgq_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ // Remove from priority queue. Not directly iterable, so use this approach
+
+ PriorityQueue::container_type::iterator new_end = dmsgq_.container().begin();
+ for (PriorityQueue::container_type::iterator it = new_end;
+ it != dmsgq_.container().end(); ++it) {
+ if (it->msg_.Match(phandler, id)) {
+ if (removed) {
+ removed->push_back(it->msg_);
+ } else {
+ delete it->msg_.pdata;
+ }
+ } else {
+ *new_end++ = *it;
+ }
+ }
+ dmsgq_.container().erase(new_end, dmsgq_.container().end());
+ dmsgq_.reheap();
+}
+
+void MessageQueue::Dispatch(Message *pmsg) {
+ pmsg->phandler->OnMessage(pmsg);
+}
+
+void MessageQueue::EnsureActive() {
+ ASSERT(crit_.CurrentThreadIsOwner());
+ if (!active_) {
+ active_ = true;
+ MessageQueueManager::Instance()->Add(this);
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/messagequeue.h b/talk/base/messagequeue.h
new file mode 100644
index 0000000..4d470df
--- /dev/null
+++ b/talk/base/messagequeue.h
@@ -0,0 +1,245 @@
+/*
+ * 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_MESSAGEQUEUE_H_
+#define TALK_BASE_MESSAGEQUEUE_H_
+
+#include <algorithm>
+#include <cstring>
+#include <list>
+#include <queue>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+struct Message;
+class MessageQueue;
+
+// MessageQueueManager does cleanup of of message queues
+
+class MessageQueueManager {
+ public:
+ static MessageQueueManager* Instance();
+
+ void Add(MessageQueue *message_queue);
+ void Remove(MessageQueue *message_queue);
+ void Clear(MessageHandler *handler);
+
+ private:
+ MessageQueueManager();
+ ~MessageQueueManager();
+
+ static MessageQueueManager* instance_;
+ // This list contains 'active' MessageQueues.
+ std::vector<MessageQueue *> message_queues_;
+ CriticalSection crit_;
+};
+
+// Derive from this for specialized data
+// App manages lifetime, except when messages are purged
+
+class MessageData {
+ public:
+ MessageData() {}
+ virtual ~MessageData() {}
+};
+
+template <class T>
+class TypedMessageData : public MessageData {
+ public:
+ explicit TypedMessageData(const T& data) : data_(data) { }
+ const T& data() const { return data_; }
+ T& data() { return data_; }
+ private:
+ T data_;
+};
+
+// Like TypedMessageData, but for pointers that require a delete.
+template <class T>
+class ScopedMessageData : public MessageData {
+ public:
+ explicit ScopedMessageData(T* data) : data_(data) { }
+ const scoped_ptr<T>& data() const { return data_; }
+ scoped_ptr<T>& data() { return data_; }
+ private:
+ scoped_ptr<T> data_;
+};
+
+template<class T>
+inline MessageData* WrapMessageData(const T& data) {
+ return new TypedMessageData<T>(data);
+}
+
+template<class T>
+inline const T& UseMessageData(MessageData* data) {
+ return static_cast< TypedMessageData<T>* >(data)->data();
+}
+
+template<class T>
+class DisposeData : public MessageData {
+ public:
+ explicit DisposeData(T* data) : data_(data) { }
+ virtual ~DisposeData() { delete data_; }
+ private:
+ T* data_;
+};
+
+const uint32 MQID_ANY = static_cast<uint32>(-1);
+const uint32 MQID_DISPOSE = static_cast<uint32>(-2);
+
+// No destructor
+
+struct Message {
+ Message() {
+ memset(this, 0, sizeof(*this));
+ }
+ inline bool Match(MessageHandler* handler, uint32 id) const {
+ return (handler == NULL || handler == phandler)
+ && (id == MQID_ANY || id == message_id);
+ }
+ MessageHandler *phandler;
+ uint32 message_id;
+ MessageData *pdata;
+ uint32 ts_sensitive;
+};
+
+typedef std::list<Message> MessageList;
+
+// DelayedMessage goes into a priority queue, sorted by trigger time. Messages
+// with the same trigger time are processed in num_ (FIFO) order.
+
+class DelayedMessage {
+ public:
+ DelayedMessage(int delay, uint32 trigger, uint32 num, const Message& msg)
+ : cmsDelay_(delay), msTrigger_(trigger), num_(num), msg_(msg) { }
+
+ bool operator< (const DelayedMessage& dmsg) const {
+ return (dmsg.msTrigger_ < msTrigger_)
+ || ((dmsg.msTrigger_ == msTrigger_) && (dmsg.num_ < num_));
+ }
+
+ int cmsDelay_; // for debugging
+ uint32 msTrigger_;
+ uint32 num_;
+ Message msg_;
+};
+
+class MessageQueue {
+ public:
+ explicit MessageQueue(SocketServer* ss = NULL);
+ virtual ~MessageQueue();
+
+ SocketServer* socketserver() { return ss_; }
+ void set_socketserver(SocketServer* ss);
+
+ // Note: The behavior of MessageQueue has changed. When a MQ is stopped,
+ // futher Posts and Sends will fail. However, any pending Sends and *ready*
+ // Posts (as opposed to unexpired delayed Posts) will be delivered before
+ // Get (or Peek) returns false. By guaranteeing delivery of those messages,
+ // we eliminate the race condition when an MessageHandler and MessageQueue
+ // may be destroyed independently of each other.
+ virtual void Quit();
+ virtual bool IsQuitting();
+ virtual void Restart();
+
+ // Get() will process I/O until:
+ // 1) A message is available (returns true)
+ // 2) cmsWait seconds have elapsed (returns false)
+ // 3) Stop() is called (returns false)
+ virtual bool Get(Message *pmsg, int cmsWait = kForever,
+ bool process_io = true);
+ virtual bool Peek(Message *pmsg, int cmsWait = 0);
+ virtual void Post(MessageHandler *phandler, uint32 id = 0,
+ MessageData *pdata = NULL, bool time_sensitive = false);
+ virtual void PostDelayed(int cmsDelay, MessageHandler *phandler,
+ uint32 id = 0, MessageData *pdata = NULL) {
+ return DoDelayPost(cmsDelay, TimeAfter(cmsDelay), phandler, id, pdata);
+ }
+ virtual void PostAt(uint32 tstamp, MessageHandler *phandler,
+ uint32 id = 0, MessageData *pdata = NULL) {
+ return DoDelayPost(TimeUntil(tstamp), tstamp, phandler, id, pdata);
+ }
+ virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+ MessageList* removed = NULL);
+ virtual void Dispatch(Message *pmsg);
+ virtual void ReceiveSends();
+
+ // Amount of time until the next message can be retrieved
+ virtual int GetDelay();
+
+ bool empty() const { return msgq_.empty() && dmsgq_.empty() && !fPeekKeep_; }
+ size_t size() const { return msgq_.size() + dmsgq_.size() + fPeekKeep_; }
+
+ // Internally posts a message which causes the doomed object to be deleted
+ template<class T> void Dispose(T* doomed) {
+ if (doomed) {
+ Post(NULL, MQID_DISPOSE, new DisposeData<T>(doomed));
+ }
+ }
+
+ // When this signal is sent out, any references to this queue should
+ // no longer be used.
+ sigslot::signal0<> SignalQueueDestroyed;
+
+ protected:
+ class PriorityQueue : public std::priority_queue<DelayedMessage> {
+ public:
+ container_type& container() { return c; }
+ void reheap() { make_heap(c.begin(), c.end(), comp); }
+ };
+
+ void EnsureActive();
+ void DoDelayPost(int cmsDelay, uint32 tstamp, MessageHandler *phandler,
+ uint32 id, MessageData* pdata);
+
+ // The SocketServer is not owned by MessageQueue.
+ SocketServer* ss_;
+ // If a server isn't supplied in the constructor, use this one.
+ scoped_ptr<SocketServer> default_ss_;
+ bool fStop_;
+ bool fPeekKeep_;
+ Message msgPeek_;
+ // A message queue is active if it has ever had a message posted to it.
+ // This also corresponds to being in MessageQueueManager's global list.
+ bool active_;
+ MessageList msgq_;
+ PriorityQueue dmsgq_;
+ uint32 dmsgq_next_num_;
+ CriticalSection crit_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_MESSAGEQUEUE_H_
diff --git a/talk/base/nethelpers.cc b/talk/base/nethelpers.cc
new file mode 100644
index 0000000..b877d78
--- /dev/null
+++ b/talk/base/nethelpers.cc
@@ -0,0 +1,175 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/nethelpers.h"
+
+#include "talk/base/byteorder.h"
+#include "talk/base/signalthread.h"
+
+namespace talk_base {
+
+#if defined(LINUX) || defined(ANDROID)
+static const size_t kInitHostentLen = 1024;
+static const size_t kMaxHostentLen = kInitHostentLen * 8;
+#endif
+
+// AsyncResolver
+
+AsyncResolver::AsyncResolver() : result_(NULL), error_(0) {
+}
+
+AsyncResolver::~AsyncResolver() {
+ FreeHostEnt(result_);
+}
+
+void AsyncResolver::DoWork() {
+ result_ = SafeGetHostByName(addr_.hostname().c_str(), &error_);
+}
+
+void AsyncResolver::OnWorkDone() {
+ if (result_) {
+ addr_.SetIP(NetworkToHost32(
+ *reinterpret_cast<uint32*>(result_->h_addr_list[0])));
+ }
+}
+
+// The functions below are used to do gethostbyname, but with an allocated
+// instead of a static buffer.
+hostent* SafeGetHostByName(const char* hostname, int* herrno) {
+ if (NULL == hostname || NULL == herrno) {
+ return NULL;
+ }
+ hostent* result = NULL;
+#if defined(WIN32)
+ // On Windows we have to allocate a buffer, and manually copy the hostent,
+ // along with its embedded pointers.
+ hostent* ent = gethostbyname(hostname);
+ if (!ent) {
+ *herrno = WSAGetLastError();
+ return NULL;
+ }
+
+ // Get the total number of bytes we need to copy, and allocate our buffer.
+ int num_aliases = 0, num_addrs = 0;
+ int total_len = sizeof(hostent);
+ total_len += strlen(ent->h_name) + 1;
+ while (ent->h_aliases[num_aliases]) {
+ total_len += sizeof(char*) + strlen(ent->h_aliases[num_aliases]) + 1;
+ ++num_aliases;
+ }
+ total_len += sizeof(char*);
+ while (ent->h_addr_list[num_addrs]) {
+ total_len += sizeof(char*) + ent->h_length;
+ ++num_addrs;
+ }
+ total_len += sizeof(char*);
+
+ result = static_cast<hostent*>(malloc(total_len));
+ if (NULL == result) {
+ return NULL;
+ }
+ char* p = reinterpret_cast<char*>(result) + sizeof(hostent);
+
+ // Copy the hostent into it, along with its embedded pointers.
+ result->h_name = p;
+ memcpy(p, ent->h_name, strlen(ent->h_name) + 1);
+ p += strlen(ent->h_name) + 1;
+
+ result->h_aliases = reinterpret_cast<char**>(p);
+ p += (num_aliases + 1) * sizeof(char*);
+ for (int i = 0; i < num_aliases; ++i) {
+ result->h_aliases[i] = p;
+ memcpy(p, ent->h_aliases[i], strlen(ent->h_aliases[i]) + 1);
+ p += strlen(ent->h_aliases[i]) + 1;
+ }
+ result->h_aliases[num_aliases] = NULL;
+
+ result->h_addrtype = ent->h_addrtype;
+ result->h_length = ent->h_length;
+
+ result->h_addr_list = reinterpret_cast<char**>(p);
+ p += (num_addrs + 1) * sizeof(char*);
+ for (int i = 0; i < num_addrs; ++i) {
+ result->h_addr_list[i] = p;
+ memcpy(p, ent->h_addr_list[i], ent->h_length);
+ p += ent->h_length;
+ }
+ result->h_addr_list[num_addrs] = NULL;
+
+ *herrno = 0;
+#elif defined(LINUX) || defined(ANDROID)
+ // gethostbyname() is not thread safe, so we need to call gethostbyname_r()
+ // which is a reentrant version of gethostbyname().
+ ASSERT(kInitHostentLen > sizeof(hostent));
+ size_t size = kInitHostentLen;
+ int ret;
+ void* buf = malloc(size);
+ if (NULL == buf) {
+ return NULL;
+ }
+ char* aux = static_cast<char*>(buf) + sizeof(hostent);
+ size_t aux_len = size - sizeof(hostent);
+ while ((ret = gethostbyname_r(hostname, reinterpret_cast<hostent*>(buf), aux,
+ aux_len, &result, herrno)) == ERANGE) {
+ size *= 2;
+ if (size > kMaxHostentLen) {
+ break; // Just to be safe.
+ }
+ buf = realloc(buf, size);
+ if (NULL == buf) {
+ return NULL;
+ }
+ aux = static_cast<char*>(buf) + sizeof(hostent);
+ aux_len = size - sizeof(hostent);
+ }
+ if (ret != 0 || buf != result) {
+ free(buf);
+ return NULL;
+ }
+ *herrno = 0;
+#elif defined(OSX) || defined(IOS)
+ // Mac OS returns an object with everything allocated.
+ result = getipnodebyname(hostname, AF_INET, AI_DEFAULT, herrno);
+#else
+#error "I don't know how to do gethostbyname safely on your system."
+#endif
+ return result;
+}
+
+// This function should mirror the above function, and free any resources
+// allocated by the above.
+void FreeHostEnt(hostent* host) {
+#if defined(OSX) || defined(IOS)
+ freehostent(host);
+#elif defined(WIN32) || defined(POSIX)
+ free(host);
+#else
+#error "I don't know how to free a hostent on your system."
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/nethelpers.h b/talk/base/nethelpers.h
new file mode 100644
index 0000000..4cba9a9
--- /dev/null
+++ b/talk/base/nethelpers.h
@@ -0,0 +1,76 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_NETHELPERS_H_
+#define TALK_BASE_NETHELPERS_H_
+
+#ifdef POSIX
+#include <netdb.h>
+#include <cstddef>
+#elif WIN32
+#include <winsock2.h> // NOLINT
+#endif
+
+#include <list>
+
+#include "talk/base/signalthread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// AsyncResolver will perform async DNS resolution, signaling the result on
+// the inherited SignalWorkDone when the operation completes.
+class AsyncResolver : public SignalThread {
+ public:
+ AsyncResolver();
+
+ const SocketAddress& address() const { return addr_; }
+ void set_address(const SocketAddress& addr) { addr_ = addr; }
+ int error() const { return error_; }
+ void set_error(int error) { error_ = error; }
+
+ protected:
+ ~AsyncResolver();
+ virtual void DoWork();
+ virtual void OnWorkDone();
+
+ private:
+ SocketAddress addr_;
+ hostent* result_;
+ int error_;
+};
+
+// SafeGetHostByName functions allocate and return their result, instead of
+// using a static variable like the normal gethostbyname.
+// FreeHostEnt frees the memory allocated by SafeGetHostByName.
+hostent* SafeGetHostByName(const char* hostname, int* herrno);
+void FreeHostEnt(hostent* host);
+
+} // namespace talk_base
+
+#endif // TALK_BASE_NETHELPERS_H_
diff --git a/talk/base/network.cc b/talk/base/network.cc
new file mode 100644
index 0000000..8a56d0a
--- /dev/null
+++ b/talk/base/network.cc
@@ -0,0 +1,456 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "talk/base/network.h"
+#include "talk/base/stream.h"
+
+#ifdef POSIX
+#include <sys/socket.h>
+#include <sys/utsname.h>
+#include <sys/ioctl.h>
+#include <net/if.h>
+#include <unistd.h>
+#include <errno.h>
+#endif // POSIX
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <Iphlpapi.h>
+#endif
+
+#include <algorithm>
+#include <cassert>
+#include <cfloat>
+#include <cmath>
+#include <cstdio>
+#include <cstring>
+#include <sstream>
+
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socket.h" // includes something that makes windows happy
+#include "talk/base/stringencode.h"
+#include "talk/base/time.h"
+
+namespace {
+
+const double kAlpha = 0.5; // weight for data infinitely far in the past
+const double kHalfLife = 2000; // half life of exponential decay (in ms)
+const double kLog2 = 0.693147180559945309417;
+const double kLambda = kLog2 / kHalfLife;
+
+// assume so-so quality unless data says otherwise
+const double kDefaultQuality = talk_base::QUALITY_FAIR;
+
+typedef std::map<std::string, std::string> StrMap;
+
+void BuildMap(const StrMap& map, std::string& str) {
+ str.append("{");
+ bool first = true;
+ for (StrMap::const_iterator i = map.begin(); i != map.end(); ++i) {
+ if (!first) str.append(",");
+ str.append(i->first);
+ str.append("=");
+ str.append(i->second);
+ first = false;
+ }
+ str.append("}");
+}
+
+void ParseCheck(std::istringstream& ist, char ch) {
+ if (ist.get() != ch)
+ LOG(LERROR) << "Expecting '" << ch << "'";
+}
+
+std::string ParseString(std::istringstream& ist) {
+ std::string str;
+ int count = 0;
+ while (ist) {
+ char ch = ist.peek();
+ if ((count == 0) && ((ch == '=') || (ch == ',') || (ch == '}'))) {
+ break;
+ } else if (ch == '{') {
+ count += 1;
+ } else if (ch == '}') {
+ count -= 1;
+ if (count < 0)
+ LOG(LERROR) << "mismatched '{' and '}'";
+ }
+ str.append(1, static_cast<char>(ist.get()));
+ }
+ return str;
+}
+
+void ParseMap(const std::string& str, StrMap& map) {
+ if (str.size() == 0)
+ return;
+ std::istringstream ist(str);
+ ParseCheck(ist, '{');
+ for (;;) {
+ std::string key = ParseString(ist);
+ ParseCheck(ist, '=');
+ std::string val = ParseString(ist);
+ map[key] = val;
+ if (ist.peek() == ',')
+ ist.get();
+ else
+ break;
+ }
+ ParseCheck(ist, '}');
+ if (ist.rdbuf()->in_avail() != 0)
+ LOG(LERROR) << "Unexpected characters at end";
+}
+
+} // namespace
+
+namespace talk_base {
+
+NetworkManager::~NetworkManager() {
+ for (NetworkMap::iterator i = networks_.begin(); i != networks_.end(); ++i)
+ delete i->second;
+}
+
+bool NetworkManager::GetNetworks(std::vector<Network*>* result) {
+ std::vector<Network*> list;
+ if (!EnumNetworks(false, &list)) {
+ return false;
+ }
+
+ for (uint32 i = 0; i < list.size(); ++i) {
+ NetworkMap::iterator iter = networks_.find(list[i]->name());
+
+ Network* network;
+ if (iter == networks_.end()) {
+ network = list[i];
+ } else {
+ network = iter->second;
+ network->set_ip(list[i]->ip());
+ network->set_gateway_ip(list[i]->gateway_ip());
+ delete list[i];
+ }
+
+ networks_[network->name()] = network;
+ result->push_back(network);
+ }
+ return true;
+}
+
+void NetworkManager::DumpNetworks(bool include_ignored) {
+ std::vector<Network*> list;
+ EnumNetworks(include_ignored, &list);
+ LOG(LS_INFO) << "NetworkManager detected " << list.size() << " networks:";
+ for (size_t i = 0; i < list.size(); ++i) {
+ const Network* network = list[i];
+ if (!network->ignored() || include_ignored) {
+ LOG(LS_INFO) << network->ToString() << ": " << network->description()
+ << ", Gateway="
+ << SocketAddress::IPToString(network->gateway_ip())
+ << ((network->ignored()) ? ", Ignored" : "");
+ }
+ }
+}
+
+std::string NetworkManager::GetState() const {
+ StrMap map;
+ for (NetworkMap::const_iterator i = networks_.begin();
+ i != networks_.end(); ++i)
+ map[i->first] = i->second->GetState();
+
+ std::string str;
+ BuildMap(map, str);
+ return str;
+}
+
+void NetworkManager::SetState(const std::string& str) {
+ StrMap map;
+ ParseMap(str, map);
+
+ for (StrMap::iterator i = map.begin(); i != map.end(); ++i) {
+ std::string name = i->first;
+ std::string state = i->second;
+
+ Network* network = new Network(name, "", 0, 0);
+ network->SetState(state);
+ networks_[name] = network;
+ }
+}
+
+#ifdef POSIX
+// Gets the default gateway for the specified interface.
+uint32 GetDefaultGateway(const std::string& name) {
+#ifdef OSX
+ // TODO: /proc/net/route doesn't exist,
+ // Use ioctl to get the routing table
+ return 0xFFFFFFFF;
+#endif
+
+ uint32 gateway_ip = 0;
+
+ FileStream fs;
+ if (fs.Open("/proc/net/route", "r")) {
+ std::string line;
+ while (fs.ReadLine(&line) == SR_SUCCESS && gateway_ip == 0) {
+ char iface[16];
+ unsigned int ip, gw;
+ if (sscanf(line.c_str(), "%7s %8X %8X", iface, &ip, &gw) == 3 &&
+ name == iface && ip == 0) {
+ gateway_ip = ntohl(gw);
+ }
+ }
+ }
+
+ return gateway_ip;
+}
+
+
+bool NetworkManager::CreateNetworks(bool include_ignored,
+ std::vector<Network*>* networks) {
+ int fd;
+ if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
+ LOG_ERR(LERROR) << "socket";
+ return false;
+ }
+
+ struct ifconf ifc;
+ ifc.ifc_len = 64 * sizeof(struct ifreq);
+ ifc.ifc_buf = new char[ifc.ifc_len];
+
+ if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) {
+ LOG_ERR(LERROR) << "ioctl";
+ return false;
+ }
+ assert(ifc.ifc_len < static_cast<int>(64 * sizeof(struct ifreq)));
+
+ struct ifreq* ptr = reinterpret_cast<struct ifreq*>(ifc.ifc_buf);
+ struct ifreq* end =
+ reinterpret_cast<struct ifreq*>(ifc.ifc_buf + ifc.ifc_len);
+
+ while (ptr < end) {
+ struct sockaddr_in* inaddr =
+ reinterpret_cast<struct sockaddr_in*>(&ptr->ifr_ifru.ifru_addr);
+ if (inaddr->sin_family == AF_INET) {
+ uint32 ip = ntohl(inaddr->sin_addr.s_addr);
+ scoped_ptr<Network> network(
+ new Network(ptr->ifr_name, ptr->ifr_name, ip,
+ GetDefaultGateway(ptr->ifr_name)));
+ network->set_ignored(IsIgnoredNetwork(*network));
+ if (include_ignored || !network->ignored()) {
+ networks->push_back(network.release());
+ }
+ }
+
+#ifdef _SIZEOF_ADDR_IFREQ
+ ptr = reinterpret_cast<struct ifreq*>(
+ reinterpret_cast<char*>(ptr) + _SIZEOF_ADDR_IFREQ(*ptr));
+#else
+ ptr++;
+#endif
+ }
+
+ delete [] ifc.ifc_buf;
+ close(fd);
+ return true;
+}
+#endif // POSIX
+
+#ifdef WIN32
+bool NetworkManager::CreateNetworks(bool include_ignored,
+ std::vector<Network*>* networks) {
+ IP_ADAPTER_INFO info_temp;
+ ULONG len = 0;
+
+ if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW)
+ // This just means there's zero networks, which is not an error.
+ return true;
+
+ scoped_array<char> buf(new char[len]);
+ IP_ADAPTER_INFO *infos = reinterpret_cast<IP_ADAPTER_INFO *>(buf.get());
+ DWORD ret = GetAdaptersInfo(infos, &len);
+ if (ret != NO_ERROR) {
+ LOG_ERR_EX(LS_ERROR, ret) << "GetAdaptersInfo failed";
+ return false;
+ }
+
+ int count = 0;
+ for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) {
+ // Ignore the loopback device.
+ if (info->Type == MIB_IF_TYPE_LOOPBACK) {
+ continue;
+ }
+
+ // In non-debug builds, don't transmit the network name because of
+ // privacy concerns. Transmit a number instead.
+ std::string name;
+#ifdef _DEBUG
+ name = info->Description;
+#else // !_DEBUG
+ std::ostringstream ost;
+ ost << count;
+ name = ost.str();
+ count++;
+#endif // !_DEBUG
+
+ scoped_ptr<Network> network(new Network(name, info->Description,
+ SocketAddress::StringToIP(info->IpAddressList.IpAddress.String),
+ SocketAddress::StringToIP(info->GatewayList.IpAddress.String)));
+ network->set_ignored(IsIgnoredNetwork(*network));
+ if (include_ignored || !network->ignored()) {
+ networks->push_back(network.release());
+ }
+ }
+
+ return true;
+}
+#endif // WIN32
+
+bool NetworkManager::IsIgnoredNetwork(const Network& network) {
+#ifdef POSIX
+ // Ignore local networks (lo, lo0, etc)
+ // Also filter out VMware interfaces, typically named vmnet1 and vmnet8
+ if (strncmp(network.name().c_str(), "lo", 2) == 0 ||
+ strncmp(network.name().c_str(), "vmnet", 5) == 0) {
+ return true;
+ }
+#elif defined(WIN32)
+ // Ignore any HOST side vmware adapters with a description like:
+ // VMware Virtual Ethernet Adapter for VMnet1
+ // but don't ignore any GUEST side adapters with a description like:
+ // VMware Accelerated AMD PCNet Adapter #2
+ if (strstr(network.description().c_str(), "VMnet") != NULL) {
+ return true;
+ }
+#endif
+
+ // Ignore any networks with a 0.x.y.z IP
+ return (network.ip() < 0x01000000);
+}
+
+bool NetworkManager::EnumNetworks(bool include_ignored,
+ std::vector<Network*>* result) {
+ return CreateNetworks(include_ignored, result);
+}
+
+
+Network::Network(const std::string& name, const std::string& desc,
+ uint32 ip, uint32 gateway_ip)
+ : name_(name), description_(desc), ip_(ip), gateway_ip_(gateway_ip),
+ ignored_(false), uniform_numerator_(0), uniform_denominator_(0),
+ exponential_numerator_(0), exponential_denominator_(0),
+ quality_(kDefaultQuality) {
+ last_data_time_ = Time();
+
+ // TODO: seed the historical data with one data point based
+ // on the link speed metric from XP (4.0 if < 50, 3.0 otherwise).
+}
+
+void Network::StartSession(NetworkSession* session) {
+ assert(std::find(sessions_.begin(), sessions_.end(), session) ==
+ sessions_.end());
+ sessions_.push_back(session);
+}
+
+void Network::StopSession(NetworkSession* session) {
+ SessionList::iterator iter =
+ std::find(sessions_.begin(), sessions_.end(), session);
+ if (iter != sessions_.end())
+ sessions_.erase(iter);
+}
+
+void Network::EstimateQuality() {
+ uint32 now = Time();
+
+ // Add new data points for the current time.
+ for (uint32 i = 0; i < sessions_.size(); ++i) {
+ if (sessions_[i]->HasQuality())
+ AddDataPoint(now, sessions_[i]->GetCurrentQuality());
+ }
+
+ // Construct the weighted average using both uniform and exponential weights.
+
+ double exp_shift = exp(-kLambda * (now - last_data_time_));
+ double numerator = uniform_numerator_ + exp_shift * exponential_numerator_;
+ double denominator = uniform_denominator_ + exp_shift *
+ exponential_denominator_;
+
+ if (denominator < DBL_EPSILON)
+ quality_ = kDefaultQuality;
+ else
+ quality_ = numerator / denominator;
+}
+
+std::string Network::ToString() const {
+ std::stringstream ss;
+ // Print out the first space-terminated token of the network desc, plus
+ // the IP address.
+ ss << "Net[" << description_.substr(0, description_.find(' '))
+ << ":" << SocketAddress::IPToString(ip_) << "]";
+ return ss.str();
+}
+
+void Network::AddDataPoint(uint32 time, double quality) {
+ uniform_numerator_ += kAlpha * quality;
+ uniform_denominator_ += kAlpha;
+
+ double exp_shift = exp(-kLambda * (time - last_data_time_));
+ exponential_numerator_ = (1 - kAlpha) * quality + exp_shift *
+ exponential_numerator_;
+ exponential_denominator_ = (1 - kAlpha) + exp_shift *
+ exponential_denominator_;
+
+ last_data_time_ = time;
+}
+
+std::string Network::GetState() const {
+ StrMap map;
+ map["lt"] = talk_base::ToString<uint32>(last_data_time_);
+ map["un"] = talk_base::ToString<double>(uniform_numerator_);
+ map["ud"] = talk_base::ToString<double>(uniform_denominator_);
+ map["en"] = talk_base::ToString<double>(exponential_numerator_);
+ map["ed"] = talk_base::ToString<double>(exponential_denominator_);
+
+ std::string str;
+ BuildMap(map, str);
+ return str;
+}
+
+void Network::SetState(const std::string& str) {
+ StrMap map;
+ ParseMap(str, map);
+
+ last_data_time_ = FromString<uint32>(map["lt"]);
+ uniform_numerator_ = FromString<double>(map["un"]);
+ uniform_denominator_ = FromString<double>(map["ud"]);
+ exponential_numerator_ = FromString<double>(map["en"]);
+ exponential_denominator_ = FromString<double>(map["ed"]);
+}
+
+} // namespace talk_base
diff --git a/talk/base/network.h b/talk/base/network.h
new file mode 100644
index 0000000..8153e54
--- /dev/null
+++ b/talk/base/network.h
@@ -0,0 +1,170 @@
+/*
+ * 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_NETWORK_H_
+#define TALK_BASE_NETWORK_H_
+
+#include <deque>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+class Network;
+class NetworkSession;
+
+// Keeps track of the available network interfaces over time so that quality
+// information can be aggregated and recorded.
+class NetworkManager {
+ public:
+ virtual ~NetworkManager();
+
+ // Updates and returns the current list of networks available on this machine.
+ // This version will make sure that repeated calls return the same object for
+ // a given network, so that quality is tracked appropriately.
+ // Does not include ignored networks.
+ bool GetNetworks(std::vector<Network*>* networks);
+
+ // Logs the available networks.
+ void DumpNetworks(bool include_ignored);
+
+ // Reads and writes the state of the quality database in a string format.
+ std::string GetState() const;
+ void SetState(const std::string& str);
+
+ // Creates a network object for each network available on the machine.
+ static bool CreateNetworks(bool include_ignored,
+ std::vector<Network*>* networks);
+ // Determines if a network should be ignored.
+ static bool IsIgnoredNetwork(const Network& network);
+
+ protected:
+ // Fills the supplied list with all usable networks. Overrideable.
+ virtual bool EnumNetworks(bool include_ignored,
+ std::vector<Network*>* networks);
+
+ private:
+ typedef std::map<std::string, Network*> NetworkMap;
+
+ NetworkMap networks_;
+};
+
+// Represents a Unix-type network interface, with a name and single address.
+// It also includes the ability to track and estimate quality.
+class Network {
+ public:
+ Network(const std::string& name, const std::string& description,
+ uint32 ip, uint32 gateway_ip);
+
+ // Returns the index of this network. This is considered the primary key
+ // that identifies each network.
+ const std::string& name() const { return name_; }
+
+ // Returns the OS-assigned name for this network. This is useful for
+ // debugging but should not be sent over the wire (for privacy reasons).
+ const std::string& description() const { return description_; }
+
+ // Identifies the current IP address used by this network.
+ uint32 ip() const { return ip_; }
+ void set_ip(uint32 ip) { ip_ = ip; }
+
+ // Identifies the current gateway IP address used by this network.
+ uint32 gateway_ip() const { return gateway_ip_; }
+ void set_gateway_ip(uint32 ip) { gateway_ip_ = ip; }
+
+ // Indicates whether this network should be ignored, perhaps because the
+ // IP/gateway is 0, or the interface is one we know is invalid.
+ bool ignored() const { return ignored_; }
+ void set_ignored(bool ignored) { ignored_ = ignored; }
+
+ // Updates the list of sessions that are ongoing.
+ void StartSession(NetworkSession* session);
+ void StopSession(NetworkSession* session);
+
+ // Re-computes the estimate of near-future quality based on the information
+ // as of this exact moment.
+ void EstimateQuality();
+
+ // Returns the current estimate of the near-future quality of connections
+ // that use this local interface.
+ double quality() { return quality_; }
+
+ // Debugging description of this network
+ std::string ToString() const;
+
+ private:
+ typedef std::vector<NetworkSession*> SessionList;
+
+ std::string name_;
+ std::string description_;
+ uint32 ip_;
+ uint32 gateway_ip_;
+ bool ignored_;
+ SessionList sessions_;
+ double uniform_numerator_;
+ double uniform_denominator_;
+ double exponential_numerator_;
+ double exponential_denominator_;
+ uint32 last_data_time_;
+ double quality_;
+
+ // Updates the statistics maintained to include the given estimate.
+ void AddDataPoint(uint32 time, double quality);
+
+ // Converts the internal state to and from a string. This is used to record
+ // quality information into a permanent store.
+ void SetState(const std::string& str);
+ std::string GetState() const;
+
+ friend class NetworkManager;
+};
+
+// Represents a session that is in progress using a particular network and can
+// provide data about the quality of the network at any given moment.
+class NetworkSession {
+ public:
+ virtual ~NetworkSession() { }
+
+ // Determines whether this session has an estimate at this moment. We will
+ // only call GetCurrentQuality when this returns true.
+ virtual bool HasQuality() = 0;
+
+ // Returns an estimate of the quality at this exact moment. The result should
+ // be a MOS (mean opinion score) value.
+ virtual float GetCurrentQuality() = 0;
+};
+
+const double QUALITY_BAD = 3.0;
+const double QUALITY_FAIR = 3.35;
+const double QUALITY_GOOD = 3.7;
+
+} // namespace talk_base
+
+#endif // TALK_BASE_NETWORK_H_
diff --git a/talk/base/openssladapter.cc b/talk/base/openssladapter.cc
new file mode 100644
index 0000000..2aacecc
--- /dev/null
+++ b/talk/base/openssladapter.cc
@@ -0,0 +1,866 @@
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/opensslv.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/openssladapter.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/Equifax_Secure_Global_eBusiness_CA-1.h"
+
+// TODO: Use a nicer abstraction for mutex.
+
+#if defined(WIN32)
+ #define MUTEX_TYPE HANDLE
+ #define MUTEX_SETUP(x) (x) = CreateMutex(NULL, FALSE, NULL)
+ #define MUTEX_CLEANUP(x) CloseHandle(x)
+ #define MUTEX_LOCK(x) WaitForSingleObject((x), INFINITE)
+ #define MUTEX_UNLOCK(x) ReleaseMutex(x)
+ #define THREAD_ID GetCurrentThreadId()
+#elif defined(_POSIX_THREADS)
+ // _POSIX_THREADS is normally defined in unistd.h if pthreads are available
+ // on your platform.
+ #define MUTEX_TYPE pthread_mutex_t
+ #define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
+ #define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
+ #define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
+ #define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
+ #define THREAD_ID pthread_self()
+#else
+ #error You must define mutex operations appropriate for your platform!
+#endif
+
+struct CRYPTO_dynlock_value {
+ MUTEX_TYPE mutex;
+};
+
+//////////////////////////////////////////////////////////////////////
+// SocketBIO
+//////////////////////////////////////////////////////////////////////
+
+static int socket_write(BIO* h, const char* buf, int num);
+static int socket_read(BIO* h, char* buf, int size);
+static int socket_puts(BIO* h, const char* str);
+static long socket_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int socket_new(BIO* h);
+static int socket_free(BIO* data);
+
+static BIO_METHOD methods_socket = {
+ BIO_TYPE_BIO,
+ "socket",
+ socket_write,
+ socket_read,
+ socket_puts,
+ 0,
+ socket_ctrl,
+ socket_new,
+ socket_free,
+ NULL,
+};
+
+BIO_METHOD* BIO_s_socket2() { return(&methods_socket); }
+
+BIO* BIO_new_socket(talk_base::AsyncSocket* socket) {
+ BIO* ret = BIO_new(BIO_s_socket2());
+ if (ret == NULL) {
+ return NULL;
+ }
+ ret->ptr = socket;
+ return ret;
+}
+
+static int socket_new(BIO* b) {
+ b->shutdown = 0;
+ b->init = 1;
+ b->num = 0; // 1 means socket closed
+ b->ptr = 0;
+ return 1;
+}
+
+static int socket_free(BIO* b) {
+ if (b == NULL)
+ return 0;
+ return 1;
+}
+
+static int socket_read(BIO* b, char* out, int outl) {
+ if (!out)
+ return -1;
+ talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+ BIO_clear_retry_flags(b);
+ int result = socket->Recv(out, outl);
+ if (result > 0) {
+ return result;
+ } else if (result == 0) {
+ b->num = 1;
+ } else if (socket->IsBlocking()) {
+ BIO_set_retry_read(b);
+ }
+ return -1;
+}
+
+static int socket_write(BIO* b, const char* in, int inl) {
+ if (!in)
+ return -1;
+ talk_base::AsyncSocket* socket = static_cast<talk_base::AsyncSocket*>(b->ptr);
+ BIO_clear_retry_flags(b);
+ int result = socket->Send(in, inl);
+ if (result > 0) {
+ return result;
+ } else if (socket->IsBlocking()) {
+ BIO_set_retry_write(b);
+ }
+ return -1;
+}
+
+static int socket_puts(BIO* b, const char* str) {
+ return socket_write(b, str, strlen(str));
+}
+
+static long socket_ctrl(BIO* b, int cmd, long num, void* ptr) {
+ UNUSED(num);
+ UNUSED(ptr);
+
+ switch (cmd) {
+ case BIO_CTRL_RESET:
+ return 0;
+ case BIO_CTRL_EOF:
+ return b->num;
+ case BIO_CTRL_WPENDING:
+ case BIO_CTRL_PENDING:
+ return 0;
+ case BIO_CTRL_FLUSH:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+// This array will store all of the mutexes available to OpenSSL.
+static MUTEX_TYPE* mutex_buf = NULL;
+
+static void locking_function(int mode, int n, const char * file, int line) {
+ if (mode & CRYPTO_LOCK) {
+ MUTEX_LOCK(mutex_buf[n]);
+ } else {
+ MUTEX_UNLOCK(mutex_buf[n]);
+ }
+}
+
+static pthread_t id_function() {
+ return THREAD_ID;
+}
+
+static CRYPTO_dynlock_value* dyn_create_function(const char* file, int line) {
+ CRYPTO_dynlock_value* value = new CRYPTO_dynlock_value;
+ if (!value)
+ return NULL;
+ MUTEX_SETUP(value->mutex);
+ return value;
+}
+
+static void dyn_lock_function(int mode, CRYPTO_dynlock_value* l,
+ const char* file, int line) {
+ if (mode & CRYPTO_LOCK) {
+ MUTEX_LOCK(l->mutex);
+ } else {
+ MUTEX_UNLOCK(l->mutex);
+ }
+}
+
+static void dyn_destroy_function(CRYPTO_dynlock_value* l,
+ const char* file, int line) {
+ MUTEX_CLEANUP(l->mutex);
+ delete l;
+}
+
+VerificationCallback OpenSSLAdapter::custom_verify_callback_ = NULL;
+
+bool OpenSSLAdapter::InitializeSSL(VerificationCallback callback) {
+ if (!InitializeSSLThread() || !SSL_library_init())
+ return false;
+ SSL_load_error_strings();
+ ERR_load_BIO_strings();
+ OpenSSL_add_all_algorithms();
+ RAND_poll();
+ custom_verify_callback_ = callback;
+ return true;
+}
+
+bool OpenSSLAdapter::InitializeSSLThread() {
+ mutex_buf = new MUTEX_TYPE[CRYPTO_num_locks()];
+ if (!mutex_buf)
+ return false;
+ for (int i = 0; i < CRYPTO_num_locks(); ++i)
+ MUTEX_SETUP(mutex_buf[i]);
+
+ // we need to cast our id_function to return an unsigned long -- pthread_t is a pointer
+ CRYPTO_set_id_callback((unsigned long (*)())id_function);
+ CRYPTO_set_locking_callback(locking_function);
+ CRYPTO_set_dynlock_create_callback(dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+ return true;
+}
+
+bool OpenSSLAdapter::CleanupSSL() {
+ if (!mutex_buf)
+ return false;
+ CRYPTO_set_id_callback(NULL);
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+ for (int i = 0; i < CRYPTO_num_locks(); ++i)
+ MUTEX_CLEANUP(mutex_buf[i]);
+ delete [] mutex_buf;
+ mutex_buf = NULL;
+ return true;
+}
+
+OpenSSLAdapter::OpenSSLAdapter(AsyncSocket* socket)
+ : SSLAdapter(socket),
+ state_(SSL_NONE),
+ ssl_read_needs_write_(false),
+ ssl_write_needs_read_(false),
+ restartable_(false),
+ ssl_(NULL), ssl_ctx_(NULL),
+ custom_verification_succeeded_(false) {
+}
+
+OpenSSLAdapter::~OpenSSLAdapter() {
+ Cleanup();
+}
+
+int
+OpenSSLAdapter::StartSSL(const char* hostname, bool restartable) {
+ if (state_ != SSL_NONE)
+ return -1;
+
+ ssl_host_name_ = hostname;
+ restartable_ = restartable;
+
+ if (socket_->GetState() != Socket::CS_CONNECTED) {
+ state_ = SSL_WAIT;
+ return 0;
+ }
+
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ Error("BeginSSL", err, false);
+ return err;
+ }
+
+ return 0;
+}
+
+int
+OpenSSLAdapter::BeginSSL() {
+ LOG(LS_INFO) << "BeginSSL: " << ssl_host_name_;
+ ASSERT(state_ == SSL_CONNECTING);
+
+ int err = 0;
+ BIO* bio = NULL;
+
+ // First set up the context
+ if (!ssl_ctx_)
+ ssl_ctx_ = SetupSSLContext();
+
+ if (!ssl_ctx_) {
+ err = -1;
+ goto ssl_error;
+ }
+
+ bio = BIO_new_socket(static_cast<AsyncSocketAdapter*>(socket_));
+ if (!bio) {
+ err = -1;
+ goto ssl_error;
+ }
+
+ ssl_ = SSL_new(ssl_ctx_);
+ if (!ssl_) {
+ err = -1;
+ goto ssl_error;
+ }
+
+ SSL_set_app_data(ssl_, this);
+
+ SSL_set_bio(ssl_, bio, bio);
+ SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+ SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ // the SSL object owns the bio now
+ bio = NULL;
+
+ // Do the connect
+ err = ContinueSSL();
+ if (err != 0)
+ goto ssl_error;
+
+ return err;
+
+ssl_error:
+ Cleanup();
+ if (bio)
+ BIO_free(bio);
+
+ return err;
+}
+
+int
+OpenSSLAdapter::ContinueSSL() {
+ LOG(LS_INFO) << "ContinueSSL";
+ ASSERT(state_ == SSL_CONNECTING);
+
+ int code = SSL_connect(ssl_);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ LOG(LS_INFO) << " -- success";
+
+ if (!SSLPostConnectionCheck(ssl_, ssl_host_name_.c_str())) {
+ LOG(LS_ERROR) << "TLS post connection check failed";
+ // make sure we close the socket
+ Cleanup();
+ // The connect failed so return -1 to shut down the socket
+ return -1;
+ }
+
+ state_ = SSL_CONNECTED;
+ AsyncSocketAdapter::OnConnectEvent(this);
+#if 0 // TODO: worry about this
+ // Don't let ourselves go away during the callbacks
+ PRefPtr<OpenSSLAdapter> lock(this);
+ LOG(LS_INFO) << " -- onStreamReadable";
+ AsyncSocketAdapter::OnReadEvent(this);
+ LOG(LS_INFO) << " -- onStreamWriteable";
+ AsyncSocketAdapter::OnWriteEvent(this);
+#endif
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ LOG(LS_INFO) << " -- error want read";
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ LOG(LS_INFO) << " -- error want write";
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ LOG(LS_INFO) << " -- error " << code;
+ return (code != 0) ? code : -1;
+ }
+
+ return 0;
+}
+
+void
+OpenSSLAdapter::Error(const char* context, int err, bool signal) {
+ LOG(LS_WARNING) << "SChannelAdapter::Error("
+ << context << ", " << err << ")";
+ state_ = SSL_ERROR;
+ SetError(err);
+ if (signal)
+ AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+void
+OpenSSLAdapter::Cleanup() {
+ LOG(LS_INFO) << "Cleanup";
+
+ state_ = SSL_NONE;
+ ssl_read_needs_write_ = false;
+ ssl_write_needs_read_ = false;
+ custom_verification_succeeded_ = false;
+
+ if (ssl_) {
+ SSL_free(ssl_);
+ ssl_ = NULL;
+ }
+
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ ssl_ctx_ = NULL;
+ }
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+OpenSSLAdapter::Send(const void* pv, size_t cb) {
+ //LOG(LS_INFO) << "OpenSSLAdapter::Send(" << cb << ")";
+
+ switch (state_) {
+ case SSL_NONE:
+ return AsyncSocketAdapter::Send(pv, cb);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ SetError(EWOULDBLOCK);
+ return SOCKET_ERROR;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_ERROR:
+ default:
+ return SOCKET_ERROR;
+ }
+
+ // OpenSSL will return an error if we try to write zero bytes
+ if (cb == 0)
+ return 0;
+
+ ssl_write_needs_read_ = false;
+
+ int code = SSL_write(ssl_, pv, cb);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ //LOG(LS_INFO) << " -- success";
+ return code;
+ case SSL_ERROR_WANT_READ:
+ //LOG(LS_INFO) << " -- error want read";
+ ssl_write_needs_read_ = true;
+ SetError(EWOULDBLOCK);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ //LOG(LS_INFO) << " -- error want write";
+ SetError(EWOULDBLOCK);
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ //LOG(LS_INFO) << " -- remote side closed";
+ SetError(EWOULDBLOCK);
+ // do we need to signal closure?
+ break;
+ default:
+ //LOG(LS_INFO) << " -- error " << code;
+ Error("SSL_write", (code ? code : -1), false);
+ break;
+ }
+
+ return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Recv(void* pv, size_t cb) {
+ //LOG(LS_INFO) << "OpenSSLAdapter::Recv(" << cb << ")";
+ switch (state_) {
+
+ case SSL_NONE:
+ return AsyncSocketAdapter::Recv(pv, cb);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ SetError(EWOULDBLOCK);
+ return SOCKET_ERROR;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_ERROR:
+ default:
+ return SOCKET_ERROR;
+ }
+
+ // Don't trust OpenSSL with zero byte reads
+ if (cb == 0)
+ return 0;
+
+ ssl_read_needs_write_ = false;
+
+ int code = SSL_read(ssl_, pv, cb);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ //LOG(LS_INFO) << " -- success";
+ return code;
+ case SSL_ERROR_WANT_READ:
+ //LOG(LS_INFO) << " -- error want read";
+ SetError(EWOULDBLOCK);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ //LOG(LS_INFO) << " -- error want write";
+ ssl_read_needs_write_ = true;
+ SetError(EWOULDBLOCK);
+ break;
+ case SSL_ERROR_ZERO_RETURN:
+ //LOG(LS_INFO) << " -- remote side closed";
+ SetError(EWOULDBLOCK);
+ // do we need to signal closure?
+ break;
+ default:
+ //LOG(LS_INFO) << " -- error " << code;
+ Error("SSL_read", (code ? code : -1), false);
+ break;
+ }
+
+ return SOCKET_ERROR;
+}
+
+int
+OpenSSLAdapter::Close() {
+ Cleanup();
+ state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+ return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+OpenSSLAdapter::GetState() const {
+ //if (signal_close_)
+ // return CS_CONNECTED;
+ ConnState state = socket_->GetState();
+ if ((state == CS_CONNECTED)
+ && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+ state = CS_CONNECTING;
+ return state;
+}
+
+void
+OpenSSLAdapter::OnConnectEvent(AsyncSocket* socket) {
+ LOG(LS_INFO) << "OpenSSLAdapter::OnConnectEvent";
+ if (state_ != SSL_WAIT) {
+ ASSERT(state_ == SSL_NONE);
+ AsyncSocketAdapter::OnConnectEvent(socket);
+ return;
+ }
+
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ AsyncSocketAdapter::OnCloseEvent(socket, err);
+ }
+}
+
+void
+OpenSSLAdapter::OnReadEvent(AsyncSocket* socket) {
+ //LOG(LS_INFO) << "OpenSSLAdapter::OnReadEvent";
+
+ if (state_ == SSL_NONE) {
+ AsyncSocketAdapter::OnReadEvent(socket);
+ return;
+ }
+
+ if (state_ == SSL_CONNECTING) {
+ if (int err = ContinueSSL()) {
+ Error("ContinueSSL", err);
+ }
+ return;
+ }
+
+ if (state_ != SSL_CONNECTED)
+ return;
+
+ // Don't let ourselves go away during the callbacks
+ //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+ if (ssl_write_needs_read_) {
+ //LOG(LS_INFO) << " -- onStreamWriteable";
+ AsyncSocketAdapter::OnWriteEvent(socket);
+ }
+
+ //LOG(LS_INFO) << " -- onStreamReadable";
+ AsyncSocketAdapter::OnReadEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnWriteEvent(AsyncSocket* socket) {
+ //LOG(LS_INFO) << "OpenSSLAdapter::OnWriteEvent";
+
+ if (state_ == SSL_NONE) {
+ AsyncSocketAdapter::OnWriteEvent(socket);
+ return;
+ }
+
+ if (state_ == SSL_CONNECTING) {
+ if (int err = ContinueSSL()) {
+ Error("ContinueSSL", err);
+ }
+ return;
+ }
+
+ if (state_ != SSL_CONNECTED)
+ return;
+
+ // Don't let ourselves go away during the callbacks
+ //PRefPtr<OpenSSLAdapter> lock(this); // TODO: fix this
+
+ if (ssl_read_needs_write_) {
+ //LOG(LS_INFO) << " -- onStreamReadable";
+ AsyncSocketAdapter::OnReadEvent(socket);
+ }
+
+ //LOG(LS_INFO) << " -- onStreamWriteable";
+ AsyncSocketAdapter::OnWriteEvent(socket);
+}
+
+void
+OpenSSLAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+ LOG(LS_INFO) << "OpenSSLAdapter::OnCloseEvent(" << err << ")";
+ AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+
+bool OpenSSLAdapter::VerifyServerName(SSL* ssl, const char* host,
+ bool ignore_bad_cert) {
+ if (!host)
+ return false;
+
+ // Checking the return from SSL_get_peer_certificate here is not strictly
+ // necessary. With our setup, it is not possible for it to return
+ // NULL. However, it is good form to check the return.
+ X509* certificate = SSL_get_peer_certificate(ssl);
+ if (!certificate)
+ return false;
+
+#ifdef _DEBUG
+ {
+ LOG(LS_INFO) << "Certificate from server:";
+ BIO* mem = BIO_new(BIO_s_mem());
+ X509_print_ex(mem, certificate, XN_FLAG_SEP_CPLUS_SPC, X509_FLAG_NO_HEADER);
+ BIO_write(mem, "\0", 1);
+ char* buffer;
+ BIO_get_mem_data(mem, &buffer);
+ LOG(LS_INFO) << buffer;
+ BIO_free(mem);
+
+ char* cipher_description =
+ SSL_CIPHER_description(SSL_get_current_cipher(ssl), NULL, 128);
+ LOG(LS_INFO) << "Cipher: " << cipher_description;
+ OPENSSL_free(cipher_description);
+ }
+#endif
+
+ bool ok = false;
+ int extension_count = X509_get_ext_count(certificate);
+ for (int i = 0; i < extension_count; ++i) {
+ X509_EXTENSION* extension = X509_get_ext(certificate, i);
+ int extension_nid = OBJ_obj2nid(X509_EXTENSION_get_object(extension));
+
+ if (extension_nid == NID_subject_alt_name) {
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ const X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#else
+ X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension);
+#endif
+ if (!meth)
+ break;
+
+ void* ext_str = NULL;
+
+ // We assign this to a local variable, instead of passing the address
+ // directly to ASN1_item_d2i.
+ // See http://readlist.com/lists/openssl.org/openssl-users/0/4761.html.
+ unsigned char* ext_value_data = extension->value->data;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+ const unsigned char **ext_value_data_ptr =
+ (const_cast<const unsigned char **>(&ext_value_data));
+#else
+ unsigned char **ext_value_data_ptr = &ext_value_data;
+#endif
+
+ if (meth->it) {
+ ext_str = ASN1_item_d2i(NULL, ext_value_data_ptr,
+ extension->value->length,
+ ASN1_ITEM_ptr(meth->it));
+ } else {
+ ext_str = meth->d2i(NULL, ext_value_data_ptr, extension->value->length);
+ }
+
+ STACK_OF(CONF_VALUE)* value = meth->i2v(meth, ext_str, NULL);
+ for (int j = 0; j < sk_CONF_VALUE_num(value); ++j) {
+ CONF_VALUE* nval = sk_CONF_VALUE_value(value, j);
+ // The value for nval can contain wildcards
+ if (!strcmp(nval->name, "DNS") && string_match(host, nval->value)) {
+ ok = true;
+ break;
+ }
+ }
+ sk_CONF_VALUE_pop_free(value, X509V3_conf_free);
+ value = NULL;
+
+ if (meth->it) {
+ ASN1_item_free(reinterpret_cast<ASN1_VALUE*>(ext_str), meth->it);
+ } else {
+ meth->ext_free(ext_str);
+ }
+ ext_str = NULL;
+ }
+ if (ok)
+ break;
+ }
+
+ char data[256];
+ X509_name_st* subject;
+ if (!ok
+ && (subject = X509_get_subject_name(certificate))
+ && (X509_NAME_get_text_by_NID(subject, NID_commonName,
+ data, sizeof(data)) > 0)) {
+ data[sizeof(data)-1] = 0;
+ if (_stricmp(data, host) == 0)
+ ok = true;
+ }
+
+ X509_free(certificate);
+
+ if (!ok && ignore_bad_cert) {
+ LOG(LS_WARNING) << "TLS certificate check FAILED. "
+ << "Allowing connection anyway.";
+ ok = true;
+ }
+
+ return ok;
+}
+
+bool OpenSSLAdapter::SSLPostConnectionCheck(SSL* ssl, const char* host) {
+ bool ok = VerifyServerName(ssl, host, ignore_bad_cert());
+
+ if (ok) {
+ ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+ custom_verification_succeeded_);
+ }
+
+ if (!ok && ignore_bad_cert()) {
+ LOG(LS_INFO) << "Other TLS post connection checks failed.";
+ ok = true;
+ }
+
+ return ok;
+}
+
+#if _DEBUG
+
+// We only use this for tracing and so it is only needed in debug mode
+
+void
+OpenSSLAdapter::SSLInfoCallback(const SSL* s, int where, int ret) {
+ const char* str = "undefined";
+ int w = where & ~SSL_ST_MASK;
+ if (w & SSL_ST_CONNECT) {
+ str = "SSL_connect";
+ } else if (w & SSL_ST_ACCEPT) {
+ str = "SSL_accept";
+ }
+ if (where & SSL_CB_LOOP) {
+ LOG(LS_INFO) << str << ":" << SSL_state_string_long(s);
+ } else if (where & SSL_CB_ALERT) {
+ str = (where & SSL_CB_READ) ? "read" : "write";
+ LOG(LS_INFO) << "SSL3 alert " << str
+ << ":" << SSL_alert_type_string_long(ret)
+ << ":" << SSL_alert_desc_string_long(ret);
+ } else if (where & SSL_CB_EXIT) {
+ if (ret == 0) {
+ LOG(LS_INFO) << str << ":failed in " << SSL_state_string_long(s);
+ } else if (ret < 0) {
+ LOG(LS_INFO) << str << ":error in " << SSL_state_string_long(s);
+ }
+ }
+}
+
+#endif // _DEBUG
+
+int
+OpenSSLAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+ if (!ok) {
+ char data[256];
+ X509* cert = X509_STORE_CTX_get_current_cert(store);
+ int depth = X509_STORE_CTX_get_error_depth(store);
+ int err = X509_STORE_CTX_get_error(store);
+
+ LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+ X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+ LOG(LS_INFO) << " issuer = " << data;
+ X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+ LOG(LS_INFO) << " subject = " << data;
+ LOG(LS_INFO) << " err = " << err
+ << ":" << X509_verify_cert_error_string(err);
+ }
+#endif
+
+ // Get our stream pointer from the store
+ SSL* ssl = reinterpret_cast<SSL*>(
+ X509_STORE_CTX_get_ex_data(store,
+ SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+ OpenSSLAdapter* stream =
+ reinterpret_cast<OpenSSLAdapter*>(SSL_get_app_data(ssl));
+
+ if (!ok && custom_verify_callback_) {
+ void* cert =
+ reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+ if (custom_verify_callback_(cert)) {
+ stream->custom_verification_succeeded_ = true;
+ LOG(LS_INFO) << "validated certificate using custom callback";
+ ok = true;
+ }
+ }
+
+ if (!ok && stream->ignore_bad_cert()) {
+ LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+ ok = 1;
+ }
+
+ return ok;
+}
+
+bool OpenSSLAdapter::ConfigureTrustedRootCertificates(SSL_CTX* ctx) {
+ // Add the root cert to the SSL context
+ // TODO: this cert appears to be the wrong one.
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+ const unsigned char* cert_buffer
+#else
+ unsigned char* cert_buffer
+#endif
+ = EquifaxSecureGlobalEBusinessCA1_certificate;
+ size_t cert_buffer_len = sizeof(EquifaxSecureGlobalEBusinessCA1_certificate);
+ X509* cert = d2i_X509(NULL, &cert_buffer, cert_buffer_len);
+ if (cert == NULL)
+ return false;
+ bool success = X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert);
+ X509_free(cert);
+ return success;
+}
+
+SSL_CTX*
+OpenSSLAdapter::SetupSSLContext() {
+ SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method());
+ if (ctx == NULL)
+ return NULL;
+
+ if (!ConfigureTrustedRootCertificates(ctx)) {
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+
+#ifdef _DEBUG
+ SSL_CTX_set_info_callback(ctx, SSLInfoCallback);
+#endif
+
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, SSLVerifyCallback);
+ SSL_CTX_set_verify_depth(ctx, 4);
+ SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+ return ctx;
+}
+
+} // namespace talk_base
+
+#endif // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/openssladapter.h b/talk/base/openssladapter.h
new file mode 100644
index 0000000..c89c292
--- /dev/null
+++ b/talk/base/openssladapter.h
@@ -0,0 +1,105 @@
+/*
+ * 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_OPENSSLADAPTER_H__
+#define TALK_BASE_OPENSSLADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLAdapter : public SSLAdapter {
+public:
+ static bool InitializeSSL(VerificationCallback callback);
+ static bool InitializeSSLThread();
+ static bool CleanupSSL();
+
+ OpenSSLAdapter(AsyncSocket* socket);
+ virtual ~OpenSSLAdapter();
+
+ virtual int StartSSL(const char* hostname, bool restartable);
+ virtual int Send(const void* pv, size_t cb);
+ virtual int Recv(void* pv, size_t cb);
+ virtual int Close();
+
+ // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+ virtual ConnState GetState() const;
+
+protected:
+ virtual void OnConnectEvent(AsyncSocket* socket);
+ virtual void OnReadEvent(AsyncSocket* socket);
+ virtual void OnWriteEvent(AsyncSocket* socket);
+ virtual void OnCloseEvent(AsyncSocket* socket, int err);
+
+private:
+ enum SSLState {
+ SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+ };
+
+ int BeginSSL();
+ int ContinueSSL();
+ void Error(const char* context, int err, bool signal = true);
+ void Cleanup();
+
+ static bool VerifyServerName(SSL* ssl, const char* host,
+ bool ignore_bad_cert);
+ bool SSLPostConnectionCheck(SSL* ssl, const char* host);
+#if _DEBUG
+ static void SSLInfoCallback(const SSL* s, int where, int ret);
+#endif // !_DEBUG
+ static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+ static VerificationCallback custom_verify_callback_;
+ friend class OpenSSLStreamAdapter; // for custom_verify_callback_;
+
+ static bool ConfigureTrustedRootCertificates(SSL_CTX* ctx);
+ static SSL_CTX* SetupSSLContext();
+
+ SSLState state_;
+ bool ssl_read_needs_write_;
+ bool ssl_write_needs_read_;
+ // If true, socket will retain SSL configuration after Close.
+ bool restartable_;
+
+ SSL* ssl_;
+ SSL_CTX* ssl_ctx_;
+ std::string ssl_host_name_;
+
+ bool custom_verification_succeeded_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPENSSLADAPTER_H__
diff --git a/talk/base/opensslidentity.cc b/talk/base/opensslidentity.cc
new file mode 100644
index 0000000..a9d94b2
--- /dev/null
+++ b/talk/base/opensslidentity.cc
@@ -0,0 +1,274 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/opensslidentity.h"
+
+#include <openssl/ssl.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/crypto.h>
+
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+
+namespace talk_base {
+
+// We could have exposed a myriad of parameters for the crypto stuff,
+// but keeping it simple seems best.
+
+// Strength of generated keys. Those are RSA.
+static const int KEY_LENGTH = 1024;
+
+// Random bits for certificate serial number
+static const int SERIAL_RAND_BITS = 64;
+
+// Certificate validity lifetime
+static const int CERTIFICATE_LIFETIME = 60*60*24*365; // one year, arbitrarily
+
+// Generate a key pair. Caller is responsible for freeing the returned object.
+static EVP_PKEY* MakeKey() {
+ LOG(LS_INFO) << "Making key pair";
+ EVP_PKEY* pkey = EVP_PKEY_new();
+#if OPENSSL_VERSION_NUMBER < 0x00908000l
+ // Only RSA_generate_key is available. Use that.
+ RSA* rsa = RSA_generate_key(KEY_LENGTH, 0x10001, NULL, NULL);
+ if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
+ EVP_PKEY_free(pkey);
+ RSA_free(rsa);
+ return NULL;
+ }
+#else
+ // RSA_generate_key is deprecated. Use _ex version.
+ BIGNUM* exponent = BN_new();
+ RSA* rsa = RSA_new();
+ if (!pkey || !exponent || !rsa ||
+ !BN_set_word(exponent, 0x10001) || // 65537 RSA exponent
+ !RSA_generate_key_ex(rsa, KEY_LENGTH, exponent, NULL) ||
+ !EVP_PKEY_assign_RSA(pkey, rsa)) {
+ EVP_PKEY_free(pkey);
+ BN_free(exponent);
+ RSA_free(rsa);
+ return NULL;
+ }
+ // ownership of rsa struct was assigned, don't free it.
+ BN_free(exponent);
+#endif
+ LOG(LS_INFO) << "Returning key pair";
+ return pkey;
+}
+
+// Generate a self-signed certificate, with the public key from the
+// given key pair. Caller is responsible for freeing the returned object.
+static X509* MakeCertificate(EVP_PKEY* pkey, const char* common_name) {
+ LOG(LS_INFO) << "Making certificate for " << common_name;
+ X509* x509 = NULL;
+ BIGNUM* serial_number = NULL;
+ X509_NAME* name = NULL;
+
+ if ((x509=X509_new()) == NULL)
+ goto error;
+
+ if (!X509_set_pubkey(x509, pkey))
+ goto error;
+
+ // serial number
+ // temporary reference to serial number inside x509 struct
+ ASN1_INTEGER* asn1_serial_number;
+ if (!(serial_number = BN_new()) ||
+ !BN_pseudo_rand(serial_number, SERIAL_RAND_BITS, 0, 0) ||
+ !(asn1_serial_number = X509_get_serialNumber(x509)) ||
+ !BN_to_ASN1_INTEGER(serial_number, asn1_serial_number))
+ goto error;
+
+ if (!X509_set_version(x509, 0L)) // version 1
+ goto error;
+
+ // There are a lot of possible components for the name entries. In
+ // our P2P SSL mode however, the certificates are pre-exchanged
+ // (through the secure XMPP channel), and so the certificate
+ // identification is arbitrary. It can't be empty, so we set some
+ // arbitrary common_name. Note that this certificate goes out in
+ // clear during SSL negotiation, so there may be a privacy issue in
+ // putting anything recognizable here.
+ if (!(name = X509_NAME_new()) ||
+ !X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8,
+ (unsigned char*)common_name, -1, -1, 0) ||
+ !X509_set_subject_name(x509, name) ||
+ !X509_set_issuer_name(x509, name))
+ goto error;
+
+ if (!X509_gmtime_adj(X509_get_notBefore(x509), 0) ||
+ !X509_gmtime_adj(X509_get_notAfter(x509), CERTIFICATE_LIFETIME))
+ goto error;
+
+ if (!X509_sign(x509, pkey, EVP_sha1()))
+ goto error;
+
+ BN_free(serial_number);
+ X509_NAME_free(name);
+ LOG(LS_INFO) << "Returning certificate";
+ return x509;
+
+ error:
+ BN_free(serial_number);
+ X509_NAME_free(name);
+ X509_free(x509);
+ return NULL;
+}
+
+// This dumps the SSL error stack to the log.
+static void LogSSLErrors(const std::string& prefix) {
+ char error_buf[200];
+ unsigned long err;
+ while ((err = ERR_get_error())) {
+ ERR_error_string_n(err, error_buf, sizeof(error_buf));
+ LOG(LS_ERROR) << prefix << ": " << error_buf << "\n";
+ }
+}
+
+OpenSSLKeyPair* OpenSSLKeyPair::Generate() {
+ EVP_PKEY* pkey = MakeKey();
+ if (!pkey) {
+ LogSSLErrors("Generating key pair");
+ return NULL;
+ }
+ return new OpenSSLKeyPair(pkey);
+}
+
+OpenSSLKeyPair::~OpenSSLKeyPair() {
+ EVP_PKEY_free(pkey_);
+}
+
+void OpenSSLKeyPair::AddReference() {
+ CRYPTO_add(&pkey_->references, 1, CRYPTO_LOCK_EVP_PKEY);
+}
+
+#ifdef _DEBUG
+// Print a certificate to the log, for debugging.
+static void PrintCert(X509* x509) {
+ BIO* temp_memory_bio = BIO_new(BIO_s_mem());
+ if (!temp_memory_bio) {
+ LOG_F(LS_ERROR) << "Failed to allocate temporary memory bio";
+ return;
+ }
+ X509_print_ex(temp_memory_bio, x509, XN_FLAG_SEP_CPLUS_SPC, 0);
+ BIO_write(temp_memory_bio, "\0", 1);
+ char* buffer;
+ BIO_get_mem_data(temp_memory_bio, &buffer);
+ LOG(LS_VERBOSE) << buffer;
+ BIO_free(temp_memory_bio);
+}
+#endif
+
+OpenSSLCertificate* OpenSSLCertificate::Generate(
+ OpenSSLKeyPair* key_pair, const std::string& common_name) {
+ std::string actual_common_name = common_name;
+ if (actual_common_name.empty())
+ // Use a random string, arbitrarily 8chars long.
+ actual_common_name = CreateRandomString(8);
+ X509* x509 = MakeCertificate(key_pair->pkey(), actual_common_name.c_str());
+ if (!x509) {
+ LogSSLErrors("Generating certificate");
+ return NULL;
+ }
+#ifdef _DEBUG
+ PrintCert(x509);
+#endif
+ return new OpenSSLCertificate(x509);
+}
+
+OpenSSLCertificate* OpenSSLCertificate::FromPEMString(
+ const std::string& pem_string, int* pem_length) {
+ BIO* bio = BIO_new_mem_buf(const_cast<char*>(pem_string.c_str()), -1);
+ if (!bio)
+ return NULL;
+ (void)BIO_set_close(bio, BIO_NOCLOSE);
+ BIO_set_mem_eof_return(bio, 0);
+ X509 *x509 = PEM_read_bio_X509(bio, NULL, NULL,
+ const_cast<char*>("\0"));
+ char *ptr;
+ int remaining_length = BIO_get_mem_data(bio, &ptr);
+ BIO_free(bio);
+ if (pem_length)
+ *pem_length = pem_string.length() - remaining_length;
+ if (x509)
+ return new OpenSSLCertificate(x509);
+ else
+ return NULL;
+}
+
+OpenSSLCertificate::~OpenSSLCertificate() {
+ X509_free(x509_);
+}
+
+std::string OpenSSLCertificate::ToPEMString() const {
+ BIO* bio = BIO_new(BIO_s_mem());
+ if (!bio)
+ return NULL;
+ if (!PEM_write_bio_X509(bio, x509_)) {
+ BIO_free(bio);
+ return NULL;
+ }
+ BIO_write(bio, "\0", 1);
+ char* buffer;
+ BIO_get_mem_data(bio, &buffer);
+ std::string ret(buffer);
+ BIO_free(bio);
+ return ret;
+}
+
+void OpenSSLCertificate::AddReference() {
+ CRYPTO_add(&x509_->references, 1, CRYPTO_LOCK_X509);
+}
+
+OpenSSLIdentity* OpenSSLIdentity::Generate(const std::string& common_name) {
+ OpenSSLKeyPair *key_pair = OpenSSLKeyPair::Generate();
+ if (key_pair) {
+ OpenSSLCertificate *certificate =
+ OpenSSLCertificate::Generate(key_pair, common_name);
+ if (certificate)
+ return new OpenSSLIdentity(key_pair, certificate);
+ delete key_pair;
+ }
+ LOG(LS_INFO) << "Identity generation failed";
+ return NULL;
+}
+
+bool OpenSSLIdentity::ConfigureIdentity(SSL_CTX* ctx) {
+ // 1 is the documented success return code.
+ if (SSL_CTX_use_certificate(ctx, certificate_->x509()) != 1 ||
+ SSL_CTX_use_PrivateKey(ctx, key_pair_->pkey()) != 1) {
+ LogSSLErrors("Configuring key and certificate");
+ return false;
+ }
+ return true;
+}
+
+} // talk_base namespace
diff --git a/talk/base/opensslidentity.h b/talk/base/opensslidentity.h
new file mode 100644
index 0000000..7cac419
--- /dev/null
+++ b/talk/base/opensslidentity.h
@@ -0,0 +1,137 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPENSSLIDENTITY_H__
+#define TALK_BASE_OPENSSLIDENTITY_H__
+
+#include <openssl/evp.h>
+#include <openssl/x509.h>
+
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sslidentity.h"
+
+typedef struct ssl_ctx_st SSL_CTX;
+
+namespace talk_base {
+
+// OpenSSLKeyPair encapsulates an OpenSSL EVP_PKEY* keypair object,
+// which is reference counted inside the OpenSSL library.
+class OpenSSLKeyPair {
+ public:
+ static OpenSSLKeyPair* Generate();
+
+ virtual ~OpenSSLKeyPair();
+
+ virtual OpenSSLKeyPair* GetReference() {
+ AddReference();
+ return new OpenSSLKeyPair(pkey_);
+ }
+
+ EVP_PKEY* pkey() const { return pkey_; }
+
+ private:
+ explicit OpenSSLKeyPair(EVP_PKEY* pkey) : pkey_(pkey) {
+ ASSERT(pkey_ != NULL);
+ }
+ void AddReference();
+
+ EVP_PKEY* pkey_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(OpenSSLKeyPair);
+};
+
+// OpenSSLCertificate encapsulates an OpenSSL X509* certificate object,
+// which is also reference counted inside the OpenSSL library.
+class OpenSSLCertificate : public SSLCertificate {
+ public:
+ static OpenSSLCertificate* Generate(OpenSSLKeyPair* key_pair,
+ const std::string& common_name);
+ static OpenSSLCertificate* FromPEMString(const std::string& pem_string,
+ int* pem_length);
+
+ virtual ~OpenSSLCertificate();
+
+ virtual OpenSSLCertificate* GetReference() {
+ AddReference();
+ return new OpenSSLCertificate(x509_);
+ }
+
+ X509* x509() const { return x509_; }
+
+ virtual std::string ToPEMString() const;
+
+ private:
+ explicit OpenSSLCertificate(X509* x509) : x509_(x509) {
+ ASSERT(x509_ != NULL);
+ }
+ void AddReference();
+
+ X509* x509_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(OpenSSLCertificate);
+};
+
+// Holds a keypair and certificate together, and a method to generate
+// them consistently.
+class OpenSSLIdentity : public SSLIdentity {
+ public:
+ static OpenSSLIdentity* Generate(const std::string& common_name);
+
+ virtual ~OpenSSLIdentity() { }
+
+ virtual OpenSSLCertificate& certificate() const {
+ return *certificate_;
+ }
+
+ virtual OpenSSLIdentity* GetReference() {
+ return new OpenSSLIdentity(key_pair_->GetReference(),
+ certificate_->GetReference());
+ }
+
+ // Configure an SSL context object to use our key and certificate.
+ bool ConfigureIdentity(SSL_CTX* ctx);
+
+ private:
+ OpenSSLIdentity(OpenSSLKeyPair* key_pair,
+ OpenSSLCertificate* certificate)
+ : key_pair_(key_pair), certificate_(certificate) {
+ ASSERT(key_pair != NULL);
+ ASSERT(certificate != NULL);
+ }
+
+ scoped_ptr<OpenSSLKeyPair> key_pair_;
+ scoped_ptr<OpenSSLCertificate> certificate_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(OpenSSLIdentity);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPENSSLIDENTITY_H__
diff --git a/talk/base/opensslstreamadapter.cc b/talk/base/opensslstreamadapter.cc
new file mode 100644
index 0000000..6a34e13
--- /dev/null
+++ b/talk/base/opensslstreamadapter.cc
@@ -0,0 +1,650 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+#if HAVE_OPENSSL_SSL_H
+
+#include "talk/base/opensslstreamadapter.h"
+
+#include <openssl/bio.h>
+#include <openssl/crypto.h>
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stream.h"
+#include "talk/base/openssladapter.h"
+#include "talk/base/opensslidentity.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// StreamBIO
+//////////////////////////////////////////////////////////////////////
+
+static int stream_write(BIO* h, const char* buf, int num);
+static int stream_read(BIO* h, char* buf, int size);
+static int stream_puts(BIO* h, const char* str);
+static long stream_ctrl(BIO* h, int cmd, long arg1, void* arg2);
+static int stream_new(BIO* h);
+static int stream_free(BIO* data);
+
+static BIO_METHOD methods_stream = {
+ BIO_TYPE_BIO,
+ "stream",
+ stream_write,
+ stream_read,
+ stream_puts,
+ 0,
+ stream_ctrl,
+ stream_new,
+ stream_free,
+ NULL,
+};
+
+static BIO_METHOD* BIO_s_stream() { return(&methods_stream); }
+
+static BIO* BIO_new_stream(StreamInterface* stream) {
+ BIO* ret = BIO_new(BIO_s_stream());
+ if (ret == NULL)
+ return NULL;
+ ret->ptr = stream;
+ return ret;
+}
+
+// bio methods return 1 (or at least non-zero) on success and 0 on failure.
+
+static int stream_new(BIO* b) {
+ b->shutdown = 0;
+ b->init = 1;
+ b->num = 0; // 1 means end-of-stream
+ b->ptr = 0;
+ return 1;
+}
+
+static int stream_free(BIO* b) {
+ if (b == NULL)
+ return 0;
+ return 1;
+}
+
+static int stream_read(BIO* b, char* out, int outl) {
+ if (!out)
+ return -1;
+ StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+ BIO_clear_retry_flags(b);
+ size_t read;
+ int error;
+ StreamResult result = stream->Read(out, outl, &read, &error);
+ if (result == SR_SUCCESS) {
+ return read;
+ } else if (result == SR_EOS) {
+ b->num = 1;
+ } else if (result == SR_BLOCK) {
+ BIO_set_retry_read(b);
+ }
+ return -1;
+}
+
+static int stream_write(BIO* b, const char* in, int inl) {
+ if (!in)
+ return -1;
+ StreamInterface* stream = static_cast<StreamInterface*>(b->ptr);
+ BIO_clear_retry_flags(b);
+ size_t written;
+ int error;
+ StreamResult result = stream->Write(in, inl, &written, &error);
+ if (result == SR_SUCCESS) {
+ return written;
+ } else if (result == SR_BLOCK) {
+ BIO_set_retry_write(b);
+ }
+ return -1;
+}
+
+static int stream_puts(BIO* b, const char* str) {
+ return stream_write(b, str, strlen(str));
+}
+
+static long stream_ctrl(BIO* b, int cmd, long num, void* ptr) {
+ UNUSED(num);
+ UNUSED(ptr);
+
+ switch (cmd) {
+ case BIO_CTRL_RESET:
+ return 0;
+ case BIO_CTRL_EOF:
+ return b->num;
+ case BIO_CTRL_WPENDING:
+ case BIO_CTRL_PENDING:
+ return 0;
+ case BIO_CTRL_FLUSH:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// OpenSSLStreamAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+OpenSSLStreamAdapter::OpenSSLStreamAdapter(StreamInterface* stream)
+ : SSLStreamAdapter(stream),
+ state_(SSL_NONE),
+ role_(SSL_CLIENT),
+ ssl_read_needs_write_(false), ssl_write_needs_read_(false),
+ ssl_(NULL), ssl_ctx_(NULL),
+ custom_verification_succeeded_(false) {
+}
+
+OpenSSLStreamAdapter::~OpenSSLStreamAdapter() {
+ Cleanup();
+}
+
+void OpenSSLStreamAdapter::SetIdentity(SSLIdentity* identity) {
+ ASSERT(identity_.get() == NULL);
+ identity_.reset(static_cast<OpenSSLIdentity*>(identity));
+}
+
+void OpenSSLStreamAdapter::SetServerRole() {
+ role_ = SSL_SERVER;
+}
+
+void OpenSSLStreamAdapter::SetPeerCertificate(SSLCertificate* cert) {
+ ASSERT(peer_certificate_.get() == NULL);
+ ASSERT(ssl_server_name_.empty());
+ peer_certificate_.reset(static_cast<OpenSSLCertificate*>(cert));
+}
+
+int OpenSSLStreamAdapter::StartSSLWithServer(const char* server_name) {
+ ASSERT(server_name != NULL && server_name[0] != '\0');
+ ssl_server_name_ = server_name;
+ return StartSSL();
+}
+
+int OpenSSLStreamAdapter::StartSSLWithPeer() {
+ ASSERT(ssl_server_name_.empty());
+ // It is permitted to specify peer_certificate_ only later.
+ return StartSSL();
+}
+
+//
+// StreamInterface Implementation
+//
+
+StreamResult OpenSSLStreamAdapter::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ LOG(LS_INFO) << "OpenSSLStreamAdapter::Write(" << data_len << ")";
+
+ switch (state_) {
+ case SSL_NONE:
+ // pass-through in clear text
+ return StreamAdapterInterface::Write(data, data_len, written, error);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ return SR_BLOCK;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_ERROR:
+ case SSL_CLOSED:
+ default:
+ if (error)
+ *error = ssl_error_code_;
+ return SR_ERROR;
+ }
+
+ // OpenSSL will return an error if we try to write zero bytes
+ if (data_len == 0) {
+ if (written)
+ *written = 0;
+ return SR_SUCCESS;
+ }
+
+ ssl_write_needs_read_ = false;
+
+ int code = SSL_write(ssl_, data, data_len);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ LOG(LS_INFO) << " -- success";
+ ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
+ if (written)
+ *written = code;
+ return SR_SUCCESS;
+ case SSL_ERROR_WANT_READ:
+ LOG(LS_INFO) << " -- error want read";
+ ssl_write_needs_read_ = true;
+ return SR_BLOCK;
+ case SSL_ERROR_WANT_WRITE:
+ LOG(LS_INFO) << " -- error want write";
+ return SR_BLOCK;
+
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ Error("SSL_write", (code ? code : -1), false);
+ if (error)
+ *error = ssl_error_code_;
+ return SR_ERROR;
+ }
+ // not reached
+}
+
+StreamResult OpenSSLStreamAdapter::Read(void* data, size_t data_len,
+ size_t* read, int* error) {
+ LOG(LS_INFO) << "OpenSSLStreamAdapter::Read(" << data_len << ")";
+ switch (state_) {
+ case SSL_NONE:
+ // pass-through in clear text
+ return StreamAdapterInterface::Read(data, data_len, read, error);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ return SR_BLOCK;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_CLOSED:
+ return SR_EOS;
+
+ case SSL_ERROR:
+ default:
+ if (error)
+ *error = ssl_error_code_;
+ return SR_ERROR;
+ }
+
+ // Don't trust OpenSSL with zero byte reads
+ if (data_len == 0) {
+ if (read)
+ *read = 0;
+ return SR_SUCCESS;
+ }
+
+ ssl_read_needs_write_ = false;
+
+ int code = SSL_read(ssl_, data, data_len);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ LOG(LS_INFO) << " -- success";
+ ASSERT(0 < code && static_cast<unsigned>(code) <= data_len);
+ if (read)
+ *read = code;
+ return SR_SUCCESS;
+ case SSL_ERROR_WANT_READ:
+ LOG(LS_INFO) << " -- error want read";
+ return SR_BLOCK;
+ case SSL_ERROR_WANT_WRITE:
+ LOG(LS_INFO) << " -- error want write";
+ ssl_read_needs_write_ = true;
+ return SR_BLOCK;
+ case SSL_ERROR_ZERO_RETURN:
+ LOG(LS_INFO) << " -- remote side closed";
+ return SR_EOS;
+ break;
+ default:
+ LOG(LS_INFO) << " -- error " << code;
+ Error("SSL_read", (code ? code : -1), false);
+ if (error)
+ *error = ssl_error_code_;
+ return SR_ERROR;
+ }
+ // not reached
+}
+
+void OpenSSLStreamAdapter::Close() {
+ Cleanup();
+ ASSERT(state_ == SSL_CLOSED || state_ == SSL_ERROR);
+ StreamAdapterInterface::Close();
+}
+
+StreamState OpenSSLStreamAdapter::GetState() const {
+ switch(state_) {
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ return SS_OPENING;
+ case SSL_CONNECTED:
+ return SS_OPEN;
+ default:
+ return SS_CLOSED;
+ };
+ // not reached
+}
+
+void OpenSSLStreamAdapter::OnEvent(StreamInterface* stream, int events,
+ int err) {
+ int events_to_signal = 0;
+ int signal_error = 0;
+ ASSERT(stream == this->stream());
+ if ((events & SE_OPEN)) {
+ LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent SE_OPEN";
+ if (state_ != SSL_WAIT) {
+ ASSERT(state_ == SSL_NONE);
+ events_to_signal |= SE_OPEN;
+ } else {
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ Error("BeginSSL", err, true);
+ return;
+ }
+ }
+ }
+ if ((events & (SE_READ|SE_WRITE))) {
+ LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent"
+ << ((events & SE_READ) ? " SE_READ" : "")
+ << ((events & SE_WRITE) ? " SE_WRITE" : "");
+ if (state_ == SSL_NONE) {
+ events_to_signal |= events & (SE_READ|SE_WRITE);
+ } else if (state_ == SSL_CONNECTING) {
+ if (int err = ContinueSSL()) {
+ Error("ContinueSSL", err, true);
+ return;
+ }
+ } else if (state_ == SSL_CONNECTED) {
+ if (((events & SE_READ) && ssl_write_needs_read_) ||
+ (events & SE_WRITE)) {
+ LOG(LS_INFO) << " -- onStreamWriteable";
+ events_to_signal |= SE_WRITE;
+ }
+ if (((events & SE_WRITE) && ssl_read_needs_write_) ||
+ (events & SE_READ)) {
+ LOG(LS_INFO) << " -- onStreamReadable";
+ events_to_signal |= SE_READ;
+ }
+ }
+ }
+ if ((events & SE_CLOSE)) {
+ LOG(LS_INFO) << "OpenSSLStreamAdapter::OnEvent(SE_CLOSE, " << err << ")";
+ Cleanup();
+ events_to_signal |= SE_CLOSE;
+ // SE_CLOSE is the only event that uses the final parameter to OnEvent().
+ ASSERT(signal_error == 0);
+ signal_error = err;
+ }
+ if(events_to_signal)
+ StreamAdapterInterface::OnEvent(stream, events_to_signal, signal_error);
+}
+
+int OpenSSLStreamAdapter::StartSSL() {
+ ASSERT(state_ == SSL_NONE);
+
+ if (StreamAdapterInterface::GetState() != SS_OPEN) {
+ state_ = SSL_WAIT;
+ return 0;
+ }
+
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ Error("BeginSSL", err, false);
+ return err;
+ }
+
+ return 0;
+}
+
+int OpenSSLStreamAdapter::BeginSSL() {
+ ASSERT(state_ == SSL_CONNECTING);
+ // The underlying stream has open. If we are in peer-to-peer mode
+ // then a peer certificate must have been specified by now.
+ ASSERT(!ssl_server_name_.empty() || peer_certificate_.get() != NULL);
+ LOG(LS_INFO) << "BeginSSL: "
+ << (!ssl_server_name_.empty() ? ssl_server_name_ :
+ "with peer");
+
+ BIO* bio = NULL;
+
+ // First set up the context
+ ASSERT(ssl_ctx_ == NULL);
+ ssl_ctx_ = SetupSSLContext();
+ if (!ssl_ctx_)
+ return -1;
+
+ bio = BIO_new_stream(static_cast<StreamInterface*>(stream()));
+ if (!bio)
+ return -1;
+
+ ssl_ = SSL_new(ssl_ctx_);
+ if (!ssl_) {
+ BIO_free(bio);
+ return -1;
+ }
+
+ SSL_set_app_data(ssl_, this);
+
+ SSL_set_bio(ssl_, bio, bio); // the SSL object owns the bio now.
+
+ SSL_set_mode(ssl_, SSL_MODE_ENABLE_PARTIAL_WRITE |
+ SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
+
+ // Do the connect
+ return ContinueSSL();
+}
+
+int OpenSSLStreamAdapter::ContinueSSL() {
+ LOG(LS_INFO) << "ContinueSSL";
+ ASSERT(state_ == SSL_CONNECTING);
+
+ int code = (role_ == SSL_CLIENT) ? SSL_connect(ssl_) : SSL_accept(ssl_);
+ switch (SSL_get_error(ssl_, code)) {
+ case SSL_ERROR_NONE:
+ LOG(LS_INFO) << " -- success";
+
+ if (!SSLPostConnectionCheck(ssl_, ssl_server_name_.c_str(),
+ peer_certificate_.get() != NULL
+ ? peer_certificate_->x509() : NULL)) {
+ LOG(LS_ERROR) << "TLS post connection check failed";
+ return -1;
+ }
+
+ state_ = SSL_CONNECTED;
+ StreamAdapterInterface::OnEvent(stream(), SE_OPEN|SE_READ|SE_WRITE, 0);
+ break;
+
+ case SSL_ERROR_WANT_READ:
+ LOG(LS_INFO) << " -- error want read";
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ LOG(LS_INFO) << " -- error want write";
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ default:
+ LOG(LS_INFO) << " -- error " << code;
+ return (code != 0) ? code : -1;
+ }
+
+ return 0;
+}
+
+void OpenSSLStreamAdapter::Error(const char* context, int err, bool signal) {
+ LOG(LS_WARNING) << "OpenSSLStreamAdapter::Error("
+ << context << ", " << err << ")";
+ state_ = SSL_ERROR;
+ ssl_error_code_ = err;
+ Cleanup();
+ if (signal)
+ StreamAdapterInterface::OnEvent(stream(), SE_CLOSE, err);
+}
+
+void OpenSSLStreamAdapter::Cleanup() {
+ LOG(LS_INFO) << "Cleanup";
+
+ if (state_ != SSL_ERROR) {
+ state_ = SSL_CLOSED;
+ ssl_error_code_ = 0;
+ }
+
+ if (ssl_) {
+ SSL_free(ssl_);
+ ssl_ = NULL;
+ }
+ if (ssl_ctx_) {
+ SSL_CTX_free(ssl_ctx_);
+ ssl_ctx_ = NULL;
+ }
+ identity_.reset();
+ peer_certificate_.reset();
+}
+
+SSL_CTX* OpenSSLStreamAdapter::SetupSSLContext() {
+ SSL_CTX* ctx = SSL_CTX_new(role_ == SSL_CLIENT ? TLSv1_client_method()
+ : TLSv1_server_method());
+ if (ctx == NULL)
+ return NULL;
+
+ if (identity_.get() && !identity_->ConfigureIdentity(ctx)) {
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+
+ if (peer_certificate_.get() == NULL) { // traditional mode
+ // Add the root cert to the SSL context
+ if(!OpenSSLAdapter::ConfigureTrustedRootCertificates(ctx)) {
+ SSL_CTX_free(ctx);
+ return NULL;
+ }
+ }
+
+ if (peer_certificate_.get() != NULL && role_ == SSL_SERVER)
+ // we must specify which client cert to ask for
+ SSL_CTX_add_client_CA(ctx, peer_certificate_->x509());
+
+#ifdef _DEBUG
+ SSL_CTX_set_info_callback(ctx, OpenSSLAdapter::SSLInfoCallback);
+#endif
+
+ SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER |SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
+ SSLVerifyCallback);
+ SSL_CTX_set_verify_depth(ctx, 4);
+ SSL_CTX_set_cipher_list(ctx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+
+ return ctx;
+}
+
+int OpenSSLStreamAdapter::SSLVerifyCallback(int ok, X509_STORE_CTX* store) {
+#if _DEBUG
+ if (!ok) {
+ char data[256];
+ X509* cert = X509_STORE_CTX_get_current_cert(store);
+ int depth = X509_STORE_CTX_get_error_depth(store);
+ int err = X509_STORE_CTX_get_error(store);
+
+ LOG(LS_INFO) << "Error with certificate at depth: " << depth;
+ X509_NAME_oneline(X509_get_issuer_name(cert), data, sizeof(data));
+ LOG(LS_INFO) << " issuer = " << data;
+ X509_NAME_oneline(X509_get_subject_name(cert), data, sizeof(data));
+ LOG(LS_INFO) << " subject = " << data;
+ LOG(LS_INFO) << " err = " << err
+ << ":" << X509_verify_cert_error_string(err);
+ }
+#endif
+
+ // Get our SSL structure from the store
+ SSL* ssl = reinterpret_cast<SSL*>(X509_STORE_CTX_get_ex_data(
+ store,
+ SSL_get_ex_data_X509_STORE_CTX_idx()));
+
+ OpenSSLStreamAdapter* stream =
+ reinterpret_cast<OpenSSLStreamAdapter*>(SSL_get_app_data(ssl));
+
+ // In peer-to-peer mode, no root cert / certificate authority was
+ // specified, so the libraries knows of no certificate to accept,
+ // and therefore it will necessarily call here on the first cert it
+ // tries to verify.
+ if (!ok && stream->peer_certificate_.get() != NULL) {
+ X509* cert = X509_STORE_CTX_get_current_cert(store);
+ int err = X509_STORE_CTX_get_error(store);
+ // peer-to-peer mode: allow the certificate to be self-signed,
+ // assuming it matches the cert that was specified.
+ if (err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT &&
+ X509_cmp(cert, stream->peer_certificate_->x509()) == 0) {
+ LOG(LS_INFO) << "Accepted self-signed peer certificate authority";
+ ok = 1;
+ }
+ } else if (!ok && OpenSSLAdapter::custom_verify_callback_) {
+ // this applies only in traditional mode
+ void* cert =
+ reinterpret_cast<void*>(X509_STORE_CTX_get_current_cert(store));
+ if (OpenSSLAdapter::custom_verify_callback_(cert)) {
+ stream->custom_verification_succeeded_ = true;
+ LOG(LS_INFO) << "validated certificate using custom callback";
+ ok = 1;
+ }
+ }
+
+ if (!ok && stream->ignore_bad_cert()) {
+ LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain";
+ ok = 1;
+ }
+
+ return ok;
+}
+
+// This code is taken from the "Network Security with OpenSSL"
+// sample in chapter 5
+bool OpenSSLStreamAdapter::SSLPostConnectionCheck(SSL* ssl,
+ const char* server_name,
+ const X509* peer_cert) {
+ ASSERT(server_name != NULL);
+ bool ok;
+ if(server_name[0] != '\0') { // traditional mode
+ ok = OpenSSLAdapter::VerifyServerName(ssl, server_name, ignore_bad_cert());
+
+ if (ok) {
+ ok = (SSL_get_verify_result(ssl) == X509_V_OK ||
+ custom_verification_succeeded_);
+ }
+ } else { // peer-to-peer mode
+ ASSERT(peer_cert != NULL);
+ // no server name validation
+ ok = true;
+ }
+
+ if (!ok && ignore_bad_cert()) {
+ LOG(LS_ERROR) << "SSL_get_verify_result(ssl) = "
+ << SSL_get_verify_result(ssl);
+ LOG(LS_INFO) << "Other TLS post connection checks failed.";
+ ok = true;
+ }
+
+ return ok;
+}
+
+} // namespace talk_base
+
+#endif // HAVE_OPENSSL_SSL_H
diff --git a/talk/base/opensslstreamadapter.h b/talk/base/opensslstreamadapter.h
new file mode 100644
index 0000000..16ec751
--- /dev/null
+++ b/talk/base/opensslstreamadapter.h
@@ -0,0 +1,171 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_OPENSSLSTREAMADAPTER_H__
+#define TALK_BASE_OPENSSLSTREAMADAPTER_H__
+
+#include <string>
+#include "talk/base/sslstreamadapter.h"
+#include "talk/base/opensslidentity.h"
+
+typedef struct ssl_st SSL;
+typedef struct ssl_ctx_st SSL_CTX;
+typedef struct x509_store_ctx_st X509_STORE_CTX;
+
+namespace talk_base {
+
+// This class was written with OpenSSLAdapter (a socket adapter) as a
+// starting point. It has similar structure and functionality, with
+// the peer-to-peer mode added.
+//
+// Static methods to initialize and deinit the SSL library are in
+// OpenSSLAdapter. This class also uses
+// OpenSSLAdapter::custom_verify_callback_ (a static field). These
+// should probably be moved out to a neutral class.
+//
+// In a few cases I have factored out some OpenSSLAdapter code into
+// static methods so it can be reused from this class. Eventually that
+// code should probably be moved to a common support
+// class. Unfortunately there remain a few duplicated sections of
+// code. I have not done more restructuring because I did not want to
+// affect existing code that uses OpenSSLAdapter.
+//
+// This class does not support the SSL connection restart feature
+// present in OpenSSLAdapter. I am not entirely sure how the feature
+// is useful and I am not convinced that it works properly.
+//
+// This implementation is careful to disallow data exchange after an
+// SSL error, and it has an explicit SSL_CLOSED state. It should not
+// be possible to send any data in clear after one of the StartSSL
+// methods has been called.
+
+// Look in sslstreamadapter.h for documentation of the methods.
+
+class OpenSSLIdentity;
+
+///////////////////////////////////////////////////////////////////////////////
+
+class OpenSSLStreamAdapter : public SSLStreamAdapter {
+ public:
+ explicit OpenSSLStreamAdapter(StreamInterface* stream);
+ virtual ~OpenSSLStreamAdapter();
+
+ virtual void SetIdentity(SSLIdentity* identity);
+ virtual void SetServerRole();
+ virtual void SetPeerCertificate(SSLCertificate* cert);
+
+ virtual int StartSSLWithServer(const char* server_name);
+ virtual int StartSSLWithPeer();
+
+ virtual StreamResult Read(void* data, size_t data_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+ virtual StreamState GetState() const;
+
+ protected:
+ virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+ private:
+ enum SSLState {
+ // Before calling one of the StartSSL methods, data flows
+ // in clear text.
+ SSL_NONE,
+ SSL_WAIT, // waiting for the stream to open to start SSL negotiation
+ SSL_CONNECTING, // SSL negotiation in progress
+ SSL_CONNECTED, // SSL stream successfully established
+ SSL_ERROR, // some SSL error occurred, stream is closed
+ SSL_CLOSED // Clean close
+ };
+ enum SSLRole {
+ SSL_CLIENT, SSL_SERVER
+ };
+
+ // The following three methods return 0 on success and a negative
+ // error code on failure. The error code may be from OpenSSL or -1
+ // on some other error cases, so it can't really be interpreted
+ // unfortunately.
+
+ // Go from state SSL_NONE to either SSL_CONNECTING or SSL_WAIT,
+ // depending on whether the underlying stream is already open or
+ // not.
+ int StartSSL();
+ // Prepare SSL library, state is SSL_CONNECTING.
+ int BeginSSL();
+ // Perform SSL negotiation steps.
+ int ContinueSSL();
+
+ // Error handler helper. signal is given as true for errors in
+ // asynchronous contexts (when an error method was not returned
+ // through some other method), and in that case an SE_CLOSE event is
+ // raised on the stream with the specified error.
+ // A 0 error means a graceful close, otherwise there is not really enough
+ // context to interpret the error code.
+ void Error(const char* context, int err, bool signal);
+ void Cleanup();
+
+ // SSL library configuration
+ SSL_CTX* SetupSSLContext();
+ // SSL verification check
+ bool SSLPostConnectionCheck(SSL* ssl, const char* server_name,
+ const X509* peer_cert);
+ // SSL certification verification error handler, called back from
+ // the openssl library. Returns an int interpreted as a boolean in
+ // the C style: zero means verification failure, non-zero means
+ // passed.
+ static int SSLVerifyCallback(int ok, X509_STORE_CTX* store);
+
+
+ SSLState state_;
+ SSLRole role_;
+ int ssl_error_code_; // valid when state_ == SSL_ERROR or SSL_CLOSED
+ // Whether the SSL negotiation is blocked on needing to read or
+ // write to the wrapped stream.
+ bool ssl_read_needs_write_;
+ bool ssl_write_needs_read_;
+
+ SSL* ssl_;
+ SSL_CTX* ssl_ctx_;
+ // in traditional mode, the server name that the server's certificate
+ // must specify. Empty in peer-to-peer mode.
+ // Our key and certificate, mostly useful in peer-to-peer mode.
+ scoped_ptr<OpenSSLIdentity> identity_;
+ std::string ssl_server_name_;
+ // In peer-to-peer mode, the certificate that the peer must
+ // present. Empty in traditional mode.
+ scoped_ptr<OpenSSLCertificate> peer_certificate_;
+
+ // OpenSSLAdapter::custom_verify_callback_ result
+ bool custom_verification_succeeded_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_OPENSSLSTREAMADAPTER_H__
diff --git a/talk/base/packetsocketfactory.h b/talk/base/packetsocketfactory.h
new file mode 100644
index 0000000..4d1f982
--- /dev/null
+++ b/talk/base/packetsocketfactory.h
@@ -0,0 +1,57 @@
+/*
+ * 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_BASE_PACKETSOCKETFACTORY_H_
+#define TALK_BASE_PACKETSOCKETFACTORY_H_
+
+#include "talk/base/proxyinfo.h"
+
+namespace talk_base {
+
+class AsyncPacketSocket;
+
+class PacketSocketFactory {
+ public:
+ PacketSocketFactory() { }
+ virtual ~PacketSocketFactory() { }
+
+ virtual AsyncPacketSocket* CreateUdpSocket(
+ const SocketAddress& address, int min_port, int max_port) = 0;
+ virtual AsyncPacketSocket* CreateServerTcpSocket(
+ const SocketAddress& local_address, int min_port, int max_port,
+ bool listen, bool ssl) = 0;
+ virtual AsyncPacketSocket* CreateClientTcpSocket(
+ const SocketAddress& local_address, const SocketAddress& remote_address,
+ const ProxyInfo& proxy_info, const std::string& user_agent, bool ssl) = 0;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(PacketSocketFactory);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PACKETSOCKETFACTORY_H_
diff --git a/talk/base/pathutils.cc b/talk/base/pathutils.cc
new file mode 100644
index 0000000..d56373b
--- /dev/null
+++ b/talk/base/pathutils.cc
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ */
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+#endif // WIN32
+
+#include "talk/base/common.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/urlencode.h"
+
+namespace talk_base {
+
+std::string const EMPTY_STR = "";
+
+// EXT_DELIM separates a file basename from extension
+const char EXT_DELIM = '.';
+
+// FOLDER_DELIMS separate folder segments and the filename
+const char* const FOLDER_DELIMS = "/\\";
+
+// DEFAULT_FOLDER_DELIM is the preferred delimiter for this platform
+#if WIN32
+const char DEFAULT_FOLDER_DELIM = '\\';
+#else // !WIN32
+const char DEFAULT_FOLDER_DELIM = '/';
+#endif // !WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa
+///////////////////////////////////////////////////////////////////////////////
+
+bool Pathname::IsFolderDelimiter(char ch) {
+ return (NULL != ::strchr(FOLDER_DELIMS, ch));
+}
+
+char Pathname::DefaultFolderDelimiter() {
+ return DEFAULT_FOLDER_DELIM;
+}
+
+Pathname::Pathname()
+ : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+}
+
+Pathname::Pathname(const std::string& pathname)
+ : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+ SetPathname(pathname);
+}
+
+Pathname::Pathname(const std::string& folder, const std::string& filename)
+ : folder_delimiter_(DEFAULT_FOLDER_DELIM) {
+ SetPathname(folder, filename);
+}
+
+void Pathname::SetFolderDelimiter(char delimiter) {
+ ASSERT(IsFolderDelimiter(delimiter));
+ folder_delimiter_ = delimiter;
+}
+
+void Pathname::Normalize() {
+ for (size_t i=0; i<folder_.length(); ++i) {
+ if (IsFolderDelimiter(folder_[i])) {
+ folder_[i] = folder_delimiter_;
+ }
+ }
+}
+
+void Pathname::clear() {
+ folder_.clear();
+ basename_.clear();
+ extension_.clear();
+}
+
+bool Pathname::empty() const {
+ return folder_.empty() && basename_.empty() && extension_.empty();
+}
+
+std::string Pathname::pathname() const {
+ std::string pathname(folder_);
+ pathname.append(basename_);
+ pathname.append(extension_);
+ if (pathname.empty()) {
+ // Instead of the empty pathname, return the current working directory.
+ pathname.push_back('.');
+ pathname.push_back(folder_delimiter_);
+ }
+ return pathname;
+}
+
+std::string Pathname::url() const {
+ std::string s = "file:///";
+ for (size_t i=0; i<folder_.length(); ++i) {
+ if (IsFolderDelimiter(folder_[i]))
+ s += '/';
+ else
+ s += folder_[i];
+ }
+ s += basename_;
+ s += extension_;
+ return UrlEncodeStringForOnlyUnsafeChars(s);
+}
+
+void Pathname::SetPathname(const std::string& pathname) {
+ std::string::size_type pos = pathname.find_last_of(FOLDER_DELIMS);
+ if (pos != std::string::npos) {
+ SetFolder(pathname.substr(0, pos + 1));
+ SetFilename(pathname.substr(pos + 1));
+ } else {
+ SetFolder(EMPTY_STR);
+ SetFilename(pathname);
+ }
+}
+
+void Pathname::SetPathname(const std::string& folder,
+ const std::string& filename) {
+ SetFolder(folder);
+ SetFilename(filename);
+}
+
+void Pathname::AppendPathname(const std::string& pathname) {
+ std::string full_pathname(folder_);
+ full_pathname.append(pathname);
+ SetPathname(full_pathname);
+}
+
+std::string Pathname::folder() const {
+ return folder_;
+}
+
+std::string Pathname::folder_name() const {
+ std::string::size_type pos = std::string::npos;
+ if (folder_.size() >= 2) {
+ pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+ }
+ if (pos != std::string::npos) {
+ return folder_.substr(pos + 1);
+ } else {
+ return folder_;
+ }
+}
+
+std::string Pathname::parent_folder() const {
+ std::string::size_type pos = std::string::npos;
+ if (folder_.size() >= 2) {
+ pos = folder_.find_last_of(FOLDER_DELIMS, folder_.length() - 2);
+ }
+ if (pos != std::string::npos) {
+ return folder_.substr(0, pos + 1);
+ } else {
+ return EMPTY_STR;
+ }
+}
+
+void Pathname::SetFolder(const std::string& folder) {
+ folder_.assign(folder);
+ // Ensure folder ends in a path delimiter
+ if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+ folder_.push_back(folder_delimiter_);
+ }
+}
+
+void Pathname::AppendFolder(const std::string& folder) {
+ folder_.append(folder);
+ // Ensure folder ends in a path delimiter
+ if (!folder_.empty() && !IsFolderDelimiter(folder_[folder_.length()-1])) {
+ folder_.push_back(folder_delimiter_);
+ }
+}
+
+std::string Pathname::basename() const {
+ return basename_;
+}
+
+bool Pathname::SetBasename(const std::string& basename) {
+ if(basename.find_first_of(FOLDER_DELIMS) != std::string::npos) {
+ return false;
+ }
+ basename_.assign(basename);
+ return true;
+}
+
+std::string Pathname::extension() const {
+ return extension_;
+}
+
+bool Pathname::SetExtension(const std::string& extension) {
+ if (extension.find_first_of(FOLDER_DELIMS) != std::string::npos ||
+ extension.find_first_of(EXT_DELIM, 1) != std::string::npos) {
+ return false;
+ }
+ extension_.assign(extension);
+ // Ensure extension begins with the extension delimiter
+ if (!extension_.empty() && (extension_[0] != EXT_DELIM)) {
+ extension_.insert(extension_.begin(), EXT_DELIM);
+ }
+ return true;
+}
+
+std::string Pathname::filename() const {
+ std::string filename(basename_);
+ filename.append(extension_);
+ return filename;
+}
+
+bool Pathname::SetFilename(const std::string& filename) {
+ std::string::size_type pos = filename.rfind(EXT_DELIM);
+ if ((pos == std::string::npos) || (pos == 0)) {
+ return SetExtension(EMPTY_STR) && SetBasename(filename);
+ } else {
+ return SetExtension(filename.substr(pos)) && SetBasename(filename.substr(0, pos));
+ }
+}
+
+#ifdef WIN32
+bool Pathname::GetDrive(char *drive, uint32 bytes) const {
+ return GetDrive(drive, bytes, folder_);
+}
+
+// static
+bool Pathname::GetDrive(char *drive, uint32 bytes,
+ const std::string& pathname) {
+ // need at lease 4 bytes to save c:
+ if (bytes < 4 || pathname.size() < 3) {
+ return false;
+ }
+
+ memcpy(drive, pathname.c_str(), 3);
+ drive[3] = 0;
+ // sanity checking
+ return (isalpha(drive[0]) &&
+ drive[1] == ':' &&
+ drive[2] == '\\');
+}
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/pathutils.h b/talk/base/pathutils.h
new file mode 100644
index 0000000..ab2aacd
--- /dev/null
+++ b/talk/base/pathutils.h
@@ -0,0 +1,180 @@
+/*
+ * 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_PATHUTILS_H__
+#define TALK_BASE_PATHUTILS_H__
+
+#include <string>
+// Temporary, until deprecated helpers are removed.
+#include "talk/base/fileutils.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Pathname - parsing of pathnames into components, and vice versa.
+//
+// To establish consistent terminology, a filename never contains a folder
+// component. A folder never contains a filename. A pathname may include
+// a folder and/or filename component. Here are some examples:
+//
+// pathname() /home/john/example.txt
+// folder() /home/john/
+// filename() example.txt
+// parent_folder() /home/
+// folder_name() john/
+// basename() example
+// extension() .txt
+//
+// Basename may begin, end, and/or include periods, but no folder delimiters.
+// If extension exists, it consists of a period followed by zero or more
+// non-period/non-delimiter characters, and basename is non-empty.
+///////////////////////////////////////////////////////////////////////////////
+
+class Pathname {
+public:
+ // Folder delimiters are slash and backslash
+ static bool IsFolderDelimiter(char ch);
+ static char DefaultFolderDelimiter();
+
+ Pathname();
+ Pathname(const std::string& pathname);
+ Pathname(const std::string& folder, const std::string& filename);
+
+ // Set's the default folder delimiter for this Pathname
+ char folder_delimiter() const { return folder_delimiter_; }
+ void SetFolderDelimiter(char delimiter);
+
+ // Normalize changes all folder delimiters to folder_delimiter()
+ void Normalize();
+
+ // Reset to the empty pathname
+ void clear();
+
+ // Returns true if the pathname is empty. Note: this->pathname().empty()
+ // is always false.
+ bool empty() const;
+
+ std::string url() const;
+
+ // Returns the folder and filename components. If the pathname is empty,
+ // returns a string representing the current directory (as a relative path,
+ // i.e., ".").
+ std::string pathname() const;
+ void SetPathname(const std::string& pathname);
+ void SetPathname(const std::string& folder, const std::string& filename);
+
+ // Append pathname to the current folder (if any). Any existing filename
+ // will be discarded.
+ void AppendPathname(const std::string& pathname);
+
+ std::string folder() const;
+ std::string folder_name() const;
+ std::string parent_folder() const;
+ // SetFolder and AppendFolder will append a folder delimiter, if needed.
+ void SetFolder(const std::string& folder);
+ void AppendFolder(const std::string& folder);
+
+ std::string basename() const;
+ bool SetBasename(const std::string& basename);
+
+ std::string extension() const;
+ // SetExtension will prefix a period, if needed.
+ bool SetExtension(const std::string& extension);
+
+ std::string filename() const;
+ bool SetFilename(const std::string& filename);
+
+#ifdef WIN32
+ bool GetDrive(char *drive, uint32 bytes) const;
+ static bool GetDrive(char *drive, uint32 bytes,const std::string& pathname);
+#endif
+
+private:
+ std::string folder_, basename_, extension_;
+ char folder_delimiter_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Global Helpers (deprecated)
+///////////////////////////////////////////////////////////////////////////////
+
+inline void SetOrganizationName(const std::string& organization) {
+ Filesystem::SetOrganizationName(organization);
+}
+inline void SetApplicationName(const std::string& application) {
+ Filesystem::SetApplicationName(application);
+}
+inline void GetOrganizationName(std::string* organization) {
+ Filesystem::GetOrganizationName(organization);
+}
+inline void GetApplicationName(std::string* application) {
+ Filesystem::GetApplicationName(application);
+}
+inline bool CreateFolder(const Pathname& path) {
+ return Filesystem::CreateFolder(path);
+}
+inline bool FinishPath(Pathname& path, bool create, const std::string& append) {
+ if (!append.empty())
+ path.AppendFolder(append);
+ return !create || CreateFolder(path);
+}
+// Note: this method uses the convention of <temp>/<appname> for the temporary
+// folder. Filesystem uses <temp>/<exename>. We will be migrating exclusively
+// to <temp>/<orgname>/<appname> eventually. Since these are temp folders,
+// it's probably ok to orphan them during the transition.
+inline bool GetTemporaryFolder(Pathname& path, bool create,
+ const std::string& append) {
+ std::string application_name;
+ Filesystem::GetApplicationName(&application_name);
+ ASSERT(!application_name.empty());
+ return Filesystem::GetTemporaryFolder(path, create, &application_name)
+ && FinishPath(path, create, append);
+}
+inline bool GetAppDataFolder(Pathname& path, bool create,
+ const std::string& append) {
+ ASSERT(!create); // TODO: Support create flag on Filesystem::GetAppDataFolder.
+ return Filesystem::GetAppDataFolder(&path, true)
+ && FinishPath(path, create, append);
+}
+inline bool CleanupTemporaryFolder() {
+ Pathname path;
+ if (!GetTemporaryFolder(path, false, ""))
+ return false;
+ if (Filesystem::IsAbsent(path))
+ return true;
+ if (!Filesystem::IsTemporaryPath(path)) {
+ ASSERT(false);
+ return false;
+ }
+ return Filesystem::DeleteFolderContents(path);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PATHUTILS_H__
diff --git a/talk/base/physicalsocketserver.cc b/talk/base/physicalsocketserver.cc
new file mode 100644
index 0000000..9cb5f4f
--- /dev/null
+++ b/talk/base/physicalsocketserver.cc
@@ -0,0 +1,1620 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <cassert>
+
+#ifdef POSIX
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <signal.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#undef SetPort
+#endif
+
+#include <algorithm>
+#include <map>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/time.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32socketinit.h"
+
+// stm: this will tell us if we are on OSX
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef POSIX
+#include <netinet/tcp.h> // for TCP_NODELAY
+#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h
+typedef void* SockOptArg;
+#endif // POSIX
+
+#ifdef WIN32
+typedef char* SockOptArg;
+#endif
+
+namespace talk_base {
+
+// Standard MTUs, from RFC 1191
+const uint16 PACKET_MAXIMUMS[] = {
+ 65535, // Theoretical maximum, Hyperchannel
+ 32000, // Nothing
+ 17914, // 16Mb IBM Token Ring
+ 8166, // IEEE 802.4
+ //4464, // IEEE 802.5 (4Mb max)
+ 4352, // FDDI
+ //2048, // Wideband Network
+ 2002, // IEEE 802.5 (4Mb recommended)
+ //1536, // Expermental Ethernet Networks
+ //1500, // Ethernet, Point-to-Point (default)
+ 1492, // IEEE 802.3
+ 1006, // SLIP, ARPANET
+ //576, // X.25 Networks
+ //544, // DEC IP Portal
+ //512, // NETBIOS
+ 508, // IEEE 802/Source-Rt Bridge, ARCNET
+ 296, // Point-to-Point (low delay)
+ 68, // Official minimum
+ 0, // End of list marker
+};
+
+const uint32 IP_HEADER_SIZE = 20;
+const uint32 ICMP_HEADER_SIZE = 8;
+
+class PhysicalSocket : public AsyncSocket, public sigslot::has_slots<> {
+ public:
+ PhysicalSocket(PhysicalSocketServer* ss, SOCKET s = INVALID_SOCKET)
+ : ss_(ss), s_(s), enabled_events_(0), error_(0),
+ state_((s == INVALID_SOCKET) ? CS_CLOSED : CS_CONNECTED),
+ resolver_(NULL) {
+#ifdef WIN32
+ // EnsureWinsockInit() ensures that winsock is initialized. The default
+ // version of this function doesn't do anything because winsock is
+ // initialized by constructor of a static object. If neccessary libjingle
+ // users can link it with a different version of this function by replacing
+ // win32socketinit.cc. See win32socketinit.cc for more details.
+ EnsureWinsockInit();
+#endif
+ if (s_ != INVALID_SOCKET) {
+ enabled_events_ = DE_READ | DE_WRITE;
+
+ int type = SOCK_STREAM;
+ socklen_t len = sizeof(type);
+ VERIFY(0 == getsockopt(s_, SOL_SOCKET, SO_TYPE, (SockOptArg)&type, &len));
+ udp_ = (SOCK_DGRAM == type);
+ }
+ }
+
+ virtual ~PhysicalSocket() {
+ Close();
+ }
+
+ // Creates the underlying OS socket (same as the "socket" function).
+ virtual bool Create(int type) {
+ Close();
+ s_ = ::socket(AF_INET, type, 0);
+ udp_ = (SOCK_DGRAM == type);
+ UpdateLastError();
+ if (udp_)
+ enabled_events_ = DE_READ | DE_WRITE;
+ return s_ != INVALID_SOCKET;
+ }
+
+ SocketAddress GetLocalAddress() const {
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+ int result = ::getsockname(s_, (sockaddr*)&addr, &addrlen);
+ SocketAddress address;
+ if (result >= 0) {
+ ASSERT(addrlen == sizeof(addr));
+ address.FromSockAddr(addr);
+ } else {
+ LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+ << s_;
+ }
+ return address;
+ }
+
+ SocketAddress GetRemoteAddress() const {
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+ int result = ::getpeername(s_, (sockaddr*)&addr, &addrlen);
+ SocketAddress address;
+ if (result >= 0) {
+ ASSERT(addrlen == sizeof(addr));
+ address.FromSockAddr(addr);
+ } else {
+ LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+ << s_;
+ }
+ return address;
+ }
+
+ int Bind(const SocketAddress& addr) {
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ int err = ::bind(s_, (sockaddr*)&saddr, sizeof(saddr));
+ UpdateLastError();
+#ifdef _DEBUG
+ if (0 == err) {
+ dbg_addr_ = "Bound @ ";
+ dbg_addr_.append(GetLocalAddress().ToString());
+ }
+#endif // _DEBUG
+ return err;
+ }
+
+ int Connect(const SocketAddress& addr) {
+ // TODO: Implicit creation is required to reconnect...
+ // ...but should we make it more explicit?
+ if ((s_ == INVALID_SOCKET) && !Create(SOCK_STREAM))
+ return SOCKET_ERROR;
+ if (addr.IsUnresolved()) {
+ if (state_ != CS_CLOSED) {
+ SetError(EALREADY);
+ return SOCKET_ERROR;
+ }
+
+ LOG(LS_VERBOSE) << "Resolving addr in PhysicalSocket::Connect";
+ resolver_ = new AsyncResolver();
+ resolver_->set_address(addr);
+ resolver_->SignalWorkDone.connect(this, &PhysicalSocket::OnResolveResult);
+ resolver_->Start();
+ state_ = CS_CONNECTING;
+ return 0;
+ }
+
+ return DoConnect(addr);
+ }
+
+ int DoConnect(const SocketAddress& addr) {
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ int err = ::connect(s_, (sockaddr*)&saddr, sizeof(saddr));
+ UpdateLastError();
+ if (err == 0) {
+ state_ = CS_CONNECTED;
+ } else if (IsBlockingError(error_)) {
+ state_ = CS_CONNECTING;
+ enabled_events_ |= DE_CONNECT;
+ } else {
+ return SOCKET_ERROR;
+ }
+
+ enabled_events_ |= DE_READ | DE_WRITE;
+ return 0;
+ }
+
+ int GetError() const {
+ return error_;
+ }
+
+ void SetError(int error) {
+ error_ = error;
+ }
+
+ ConnState GetState() const {
+ return state_;
+ }
+
+ int GetOption(Option opt, int* value) {
+ int slevel;
+ int sopt;
+ if (TranslateOption(opt, &slevel, &sopt) == -1)
+ return -1;
+ socklen_t optlen = sizeof(*value);
+ int ret = ::getsockopt(s_, slevel, sopt, (SockOptArg)value, &optlen);
+ if (ret != -1 && opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+ *value = (*value != IP_PMTUDISC_DONT) ? 1 : 0;
+#endif
+ }
+ return ret;
+ }
+
+ int SetOption(Option opt, int value) {
+ int slevel;
+ int sopt;
+ if (TranslateOption(opt, &slevel, &sopt) == -1)
+ return -1;
+ if (opt == OPT_DONTFRAGMENT) {
+#ifdef LINUX
+ value = (value) ? IP_PMTUDISC_DO : IP_PMTUDISC_DONT;
+#endif
+ }
+ return ::setsockopt(s_, slevel, sopt, (SockOptArg)&value, sizeof(value));
+ }
+
+ int Send(const void *pv, size_t cb) {
+ int sent = ::send(s_, reinterpret_cast<const char *>(pv), (int)cb,
+#ifdef LINUX
+ // Suppress SIGPIPE. Without this, attempting to send on a socket whose
+ // other end is closed will result in a SIGPIPE signal being raised to
+ // our process, which by default will terminate the process, which we
+ // don't want. By specifying this flag, we'll just get the error EPIPE
+ // instead and can handle the error gracefully.
+ MSG_NOSIGNAL
+#else
+ 0
+#endif
+ );
+ UpdateLastError();
+ // We have seen minidumps where this may be false.
+ ASSERT(sent <= static_cast<int>(cb));
+ if ((sent < 0) && IsBlockingError(error_)) {
+ enabled_events_ |= DE_WRITE;
+ }
+ return sent;
+ }
+
+ int SendTo(const void *pv, size_t cb, const SocketAddress& addr) {
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ int sent = ::sendto(
+ s_, (const char *)pv, (int)cb,
+#ifdef LINUX
+ // Suppress SIGPIPE. See above for explanation.
+ MSG_NOSIGNAL,
+#else
+ 0,
+#endif
+ (sockaddr*)&saddr, sizeof(saddr));
+ UpdateLastError();
+ // We have seen minidumps where this may be false.
+ ASSERT(sent <= static_cast<int>(cb));
+ if ((sent < 0) && IsBlockingError(error_)) {
+ enabled_events_ |= DE_WRITE;
+ }
+ return sent;
+ }
+
+ int Recv(void *pv, size_t cb) {
+ int received = ::recv(s_, (char *)pv, (int)cb, 0);
+ if ((received == 0) && (cb != 0)) {
+ // Note: on graceful shutdown, recv can return 0. In this case, we
+ // pretend it is blocking, and then signal close, so that simplifying
+ // assumptions can be made about Recv.
+ LOG(LS_WARNING) << "EOF from socket; deferring close event";
+ // Must turn this back on so that the select() loop will notice the close
+ // event.
+ enabled_events_ |= DE_READ;
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ UpdateLastError();
+ bool success = (received >= 0) || IsBlockingError(error_);
+ if (udp_ || success) {
+ enabled_events_ |= DE_READ;
+ }
+ if (!success) {
+ LOG_F(LS_VERBOSE) << "Error = " << error_;
+ }
+ return received;
+ }
+
+ int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+ sockaddr_in saddr;
+ socklen_t cbAddr = sizeof(saddr);
+ int received = ::recvfrom(s_, (char *)pv, (int)cb, 0, (sockaddr*)&saddr,
+ &cbAddr);
+ UpdateLastError();
+ if ((received >= 0) && (paddr != NULL))
+ paddr->FromSockAddr(saddr);
+ bool success = (received >= 0) || IsBlockingError(error_);
+ if (udp_ || success) {
+ enabled_events_ |= DE_READ;
+ }
+ if (!success) {
+ LOG_F(LS_VERBOSE) << "Error = " << error_;
+ }
+ return received;
+ }
+
+ int Listen(int backlog) {
+ int err = ::listen(s_, backlog);
+ UpdateLastError();
+ if (err == 0) {
+ state_ = CS_CONNECTING;
+ enabled_events_ |= DE_ACCEPT;
+#ifdef _DEBUG
+ dbg_addr_ = "Listening @ ";
+ dbg_addr_.append(GetLocalAddress().ToString());
+#endif // _DEBUG
+ }
+ return err;
+ }
+
+ AsyncSocket* Accept(SocketAddress *paddr) {
+ sockaddr_in saddr;
+ socklen_t cbAddr = sizeof(saddr);
+ SOCKET s = ::accept(s_, (sockaddr*)&saddr, &cbAddr);
+ UpdateLastError();
+ if (s == INVALID_SOCKET)
+ return NULL;
+ enabled_events_ |= DE_ACCEPT;
+ if (paddr != NULL)
+ paddr->FromSockAddr(saddr);
+ return ss_->WrapSocket(s);
+ }
+
+ int Close() {
+ if (s_ == INVALID_SOCKET)
+ return 0;
+ int err = ::closesocket(s_);
+ UpdateLastError();
+ s_ = INVALID_SOCKET;
+ state_ = CS_CLOSED;
+ enabled_events_ = 0;
+ if (resolver_) {
+ resolver_->Destroy(false);
+ resolver_ = NULL;
+ }
+ return err;
+ }
+
+ int EstimateMTU(uint16* mtu) {
+ SocketAddress addr = GetRemoteAddress();
+ if (addr.IsAny()) {
+ error_ = ENOTCONN;
+ return -1;
+ }
+
+#if defined(WIN32)
+ // Gets the interface MTU (TTL=1) for the interface used to reach |addr|.
+ WinPing ping;
+ if (!ping.IsValid()) {
+ error_ = EINVAL; // can't think of a better error ID
+ return -1;
+ }
+
+ for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+ int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE;
+ WinPing::PingResult result = ping.Ping(addr.ip(), size, 0, 1, false);
+ if (result == WinPing::PING_FAIL) {
+ error_ = EINVAL; // can't think of a better error ID
+ return -1;
+ } else if (result != WinPing::PING_TOO_LARGE) {
+ *mtu = PACKET_MAXIMUMS[level];
+ return 0;
+ }
+ }
+
+ ASSERT(false);
+ return -1;
+#elif defined(IOS) || defined(OSX)
+ // No simple way to do this on Mac OS X.
+ // SIOCGIFMTU would work if we knew which interface would be used, but
+ // figuring that out is pretty complicated. For now we'll return an error
+ // and let the caller pick a default MTU.
+ error_ = EINVAL;
+ return -1;
+#elif defined(LINUX) || defined(ANDROID)
+ // Gets the path MTU.
+ int value;
+ socklen_t vlen = sizeof(value);
+ int err = getsockopt(s_, IPPROTO_IP, IP_MTU, &value, &vlen);
+ if (err < 0) {
+ UpdateLastError();
+ return err;
+ }
+
+ ASSERT((0 <= value) && (value <= 65536));
+ *mtu = value;
+ return 0;
+#endif
+ }
+
+ SocketServer* socketserver() { return ss_; }
+
+ protected:
+ void OnResolveResult(SignalThread* thread) {
+ if (thread != resolver_) {
+ return;
+ }
+
+ int error = resolver_->error();
+ if (error == 0) {
+ error = DoConnect(resolver_->address());
+ } else {
+ Close();
+ }
+
+ if (error) {
+ error_ = error;
+ SignalCloseEvent(this, error_);
+ }
+ }
+
+ void UpdateLastError() {
+ error_ = LAST_SYSTEM_ERROR;
+ }
+
+ static int TranslateOption(Option opt, int* slevel, int* sopt) {
+ switch (opt) {
+ case OPT_DONTFRAGMENT:
+#ifdef WIN32
+ *slevel = IPPROTO_IP;
+ *sopt = IP_DONTFRAGMENT;
+ break;
+#elif defined(IOS) || defined(OSX) || defined(BSD)
+ LOG(LS_WARNING) << "Socket::OPT_DONTFRAGMENT not supported.";
+ return -1;
+#elif defined(POSIX)
+ *slevel = IPPROTO_IP;
+ *sopt = IP_MTU_DISCOVER;
+ break;
+#endif
+ case OPT_RCVBUF:
+ *slevel = SOL_SOCKET;
+ *sopt = SO_RCVBUF;
+ break;
+ case OPT_SNDBUF:
+ *slevel = SOL_SOCKET;
+ *sopt = SO_SNDBUF;
+ break;
+ case OPT_NODELAY:
+ *slevel = IPPROTO_TCP;
+ *sopt = TCP_NODELAY;
+ break;
+ default:
+ ASSERT(false);
+ return -1;
+ }
+ return 0;
+ }
+
+ PhysicalSocketServer* ss_;
+ SOCKET s_;
+ uint8 enabled_events_;
+ bool udp_;
+ int error_;
+ ConnState state_;
+ AsyncResolver* resolver_;
+
+#ifdef _DEBUG
+ std::string dbg_addr_;
+#endif // _DEBUG;
+};
+
+#ifdef POSIX
+class EventDispatcher : public Dispatcher {
+ public:
+ EventDispatcher(PhysicalSocketServer* ss) : ss_(ss), fSignaled_(false) {
+ if (pipe(afd_) < 0)
+ LOG(LERROR) << "pipe failed";
+ ss_->Add(this);
+ }
+
+ virtual ~EventDispatcher() {
+ ss_->Remove(this);
+ close(afd_[0]);
+ close(afd_[1]);
+ }
+
+ virtual void Signal() {
+ CritScope cs(&crit_);
+ if (!fSignaled_) {
+ const uint8 b[1] = { 0 };
+ if (VERIFY(1 == write(afd_[1], b, sizeof(b)))) {
+ fSignaled_ = true;
+ }
+ }
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return DE_READ;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ // It is not possible to perfectly emulate an auto-resetting event with
+ // pipes. This simulates it by resetting before the event is handled.
+
+ CritScope cs(&crit_);
+ if (fSignaled_) {
+ uint8 b[4]; // Allow for reading more than 1 byte, but expect 1.
+ VERIFY(1 == read(afd_[0], b, sizeof(b)));
+ fSignaled_ = false;
+ }
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ ASSERT(false);
+ }
+
+ virtual int GetDescriptor() {
+ return afd_[0];
+ }
+
+ virtual bool IsDescriptorClosed() {
+ return false;
+ }
+
+ private:
+ PhysicalSocketServer *ss_;
+ int afd_[2];
+ bool fSignaled_;
+ CriticalSection crit_;
+};
+
+// These two classes use the self-pipe trick to deliver POSIX signals to our
+// select loop. This is the only safe, reliable, cross-platform way to do
+// non-trivial things with a POSIX signal in an event-driven program (until
+// proper pselect() implementations become ubiquitous).
+
+class PosixSignalHandler {
+ public:
+ // POSIX only specifies 32 signals, but in principle the system might have
+ // more and the programmer might choose to use them, so we size our array
+ // for 128.
+ static const int kNumPosixSignals = 128;
+
+ static PosixSignalHandler *Instance() { return &instance_; }
+
+ // Returns true if the given signal number is set.
+ bool IsSignalSet(int signum) const {
+ ASSERT(signum < ARRAY_SIZE(received_signal_));
+ if (signum < ARRAY_SIZE(received_signal_)) {
+ return received_signal_[signum];
+ } else {
+ return false;
+ }
+ }
+
+ // Clears the given signal number.
+ void ClearSignal(int signum) {
+ ASSERT(signum < ARRAY_SIZE(received_signal_));
+ if (signum < ARRAY_SIZE(received_signal_)) {
+ received_signal_[signum] = false;
+ }
+ }
+
+ // Returns the file descriptor to monitor for signal events.
+ int GetDescriptor() const {
+ return afd_[0];
+ }
+
+ // This is called directly from our real signal handler, so it must be
+ // signal-handler-safe. That means it cannot assume anything about the
+ // user-level state of the process, since the handler could be executed at any
+ // time on any thread.
+ void OnPosixSignalReceived(int signum) {
+ if (signum >= ARRAY_SIZE(received_signal_)) {
+ // We don't have space in our array for this.
+ return;
+ }
+ // Set a flag saying we've seen this signal.
+ received_signal_[signum] = true;
+ // Notify application code that we got a signal.
+ const uint8 b[1] = { 0 };
+ if (-1 == write(afd_[1], b, sizeof(b))) {
+ // Nothing we can do here. If there's an error somehow then there's
+ // nothing we can safely do from a signal handler.
+ // No, we can't even safely log it.
+ // But, we still have to check the return value here. Otherwise,
+ // GCC 4.4.1 complains ignoring return value. Even (void) doesn't help.
+ return;
+ }
+ }
+
+ private:
+ PosixSignalHandler() {
+ if (pipe(afd_) < 0) {
+ LOG_ERR(LS_ERROR) << "pipe failed";
+ return;
+ }
+ if (fcntl(afd_[0], F_SETFL, O_NONBLOCK) < 0) {
+ LOG_ERR(LS_WARNING) << "fcntl #1 failed";
+ }
+ if (fcntl(afd_[1], F_SETFL, O_NONBLOCK) < 0) {
+ LOG_ERR(LS_WARNING) << "fcntl #2 failed";
+ }
+ memset(const_cast<void *>(static_cast<volatile void *>(received_signal_)),
+ 0,
+ sizeof(received_signal_));
+ }
+
+ ~PosixSignalHandler() {
+ int fd1 = afd_[0];
+ int fd2 = afd_[1];
+ // We clobber the stored file descriptor numbers here or else in principle
+ // a signal that happens to be delivered during application termination
+ // could erroneously write a zero byte to an unrelated file handle in
+ // OnPosixSignalReceived() if some other file happens to be opened later
+ // during shutdown and happens to be given the same file descriptor number
+ // as our pipe had. Unfortunately even with this precaution there is still a
+ // race where that could occur if said signal happens to be handled
+ // concurrently with this code and happens to have already read the value of
+ // afd_[1] from memory before we clobber it, but that's unlikely.
+ afd_[0] = -1;
+ afd_[1] = -1;
+ close(fd1);
+ close(fd2);
+ }
+
+ // There is just a single global instance. (Signal handlers do not get any
+ // sort of user-defined void * parameter, so they can't access anything that
+ // isn't global.)
+ static PosixSignalHandler instance_;
+
+ int afd_[2];
+ // These are boolean flags that will be set in our signal handler and read
+ // and cleared from Wait(). There is a race involved in this, but it is
+ // benign. The signal handler sets the flag before signaling the pipe, so
+ // we'll never end up blocking in select() while a flag is still true.
+ // However, if two of the same signal arrive close to each other then it's
+ // possible that the second time the handler may set the flag while it's still
+ // true, meaning that signal will be missed. But the first occurrence of it
+ // will still be handled, so this isn't a problem.
+ // Volatile is not necessary here for correctness, but this data _is_ volatile
+ // so I've marked it as such.
+ volatile uint8 received_signal_[kNumPosixSignals];
+};
+
+PosixSignalHandler PosixSignalHandler::instance_;
+
+class PosixSignalDispatcher : public Dispatcher {
+ public:
+ PosixSignalDispatcher(PhysicalSocketServer *owner) : owner_(owner) {
+ owner_->Add(this);
+ }
+
+ virtual ~PosixSignalDispatcher() {
+ owner_->Remove(this);
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return DE_READ;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ // Events might get grouped if signals come very fast, so we read out up to
+ // 16 bytes to make sure we keep the pipe empty.
+ uint8 b[16];
+ ssize_t ret = read(GetDescriptor(), b, sizeof(b));
+ if (ret < 0) {
+ LOG_ERR(LS_WARNING) << "Error in read()";
+ } else if (ret == 0) {
+ LOG(LS_WARNING) << "Should have read at least one byte";
+ }
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ for (int signum = 0; signum < PosixSignalHandler::kNumPosixSignals;
+ ++signum) {
+ if (PosixSignalHandler::Instance()->IsSignalSet(signum)) {
+ PosixSignalHandler::Instance()->ClearSignal(signum);
+ HandlerMap::iterator i = handlers_.find(signum);
+ if (i == handlers_.end()) {
+ // This can happen if a signal is delivered to our process at around
+ // the same time as we unset our handler for it. It is not an error
+ // condition, but it's unusual enough to be worth logging.
+ LOG(LS_INFO) << "Received signal with no handler: " << signum;
+ } else {
+ // Otherwise, execute our handler.
+ (*i->second)(signum);
+ }
+ }
+ }
+ }
+
+ virtual int GetDescriptor() {
+ return PosixSignalHandler::Instance()->GetDescriptor();
+ }
+
+ virtual bool IsDescriptorClosed() {
+ return false;
+ }
+
+ void SetHandler(int signum, void (*handler)(int)) {
+ handlers_[signum] = handler;
+ }
+
+ void ClearHandler(int signum) {
+ handlers_.erase(signum);
+ }
+
+ bool HasHandlers() {
+ return !handlers_.empty();
+ }
+
+ private:
+ typedef std::map<int, void (*)(int)> HandlerMap;
+
+ HandlerMap handlers_;
+ // Our owner.
+ PhysicalSocketServer *owner_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+ explicit SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) {
+ }
+ SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) {
+ }
+
+ virtual ~SocketDispatcher() {
+ Close();
+ }
+
+ bool Initialize() {
+ ss_->Add(this);
+ fcntl(s_, F_SETFL, fcntl(s_, F_GETFL, 0) | O_NONBLOCK);
+ return true;
+ }
+
+ virtual bool Create(int type) {
+ // Change the socket to be non-blocking.
+ if (!PhysicalSocket::Create(type))
+ return false;
+
+ return Initialize();
+ }
+
+ virtual int GetDescriptor() {
+ return s_;
+ }
+
+ virtual bool IsDescriptorClosed() {
+ // We don't have a reliable way of distinguishing end-of-stream
+ // from readability. So test on each readable call. Is this
+ // inefficient? Probably.
+ char ch;
+ ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK);
+ if (res > 0) {
+ // Data available, so not closed.
+ return false;
+ } else if (res == 0) {
+ // EOF, so closed.
+ return true;
+ } else { // error
+ switch (errno) {
+ // Returned if we've already closed s_.
+ case EBADF:
+ // Returned during ungraceful peer shutdown.
+ case ECONNRESET:
+ return true;
+ default:
+ // Assume that all other errors are just blocking errors, meaning the
+ // connection is still good but we just can't read from it right now.
+ // This should only happen when connecting (and at most once), because
+ // in all other cases this function is only called if the file
+ // descriptor is already known to be in the readable state. However,
+ // it's not necessary a problem if we spuriously interpret a
+ // "connection lost"-type error as a blocking error, because typically
+ // the next recv() will get EOF, so we'll still eventually notice that
+ // the socket is closed.
+ LOG_ERR(LS_WARNING) << "Assuming benign blocking error";
+ return false;
+ }
+ }
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return enabled_events_;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ if ((ff & DE_CONNECT) != 0)
+ state_ = CS_CONNECTED;
+ if ((ff & DE_CLOSE) != 0)
+ state_ = CS_CLOSED;
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ if ((ff & DE_READ) != 0) {
+ enabled_events_ &= ~DE_READ;
+ SignalReadEvent(this);
+ }
+ if ((ff & DE_WRITE) != 0) {
+ enabled_events_ &= ~DE_WRITE;
+ SignalWriteEvent(this);
+ }
+ if ((ff & DE_CONNECT) != 0) {
+ enabled_events_ &= ~DE_CONNECT;
+ SignalConnectEvent(this);
+ }
+ if ((ff & DE_ACCEPT) != 0) {
+ enabled_events_ &= ~DE_ACCEPT;
+ SignalReadEvent(this);
+ }
+ if ((ff & DE_CLOSE) != 0) {
+ // The socket is now dead to us, so stop checking it.
+ enabled_events_ = 0;
+ SignalCloseEvent(this, err);
+ }
+ }
+
+ virtual int Close() {
+ if (s_ == INVALID_SOCKET)
+ return 0;
+
+ ss_->Remove(this);
+ return PhysicalSocket::Close();
+ }
+};
+
+class FileDispatcher: public Dispatcher, public AsyncFile {
+ public:
+ FileDispatcher(int fd, PhysicalSocketServer *ss) : ss_(ss), fd_(fd) {
+ set_readable(true);
+
+ ss_->Add(this);
+
+ fcntl(fd_, F_SETFL, fcntl(fd_, F_GETFL, 0) | O_NONBLOCK);
+ }
+
+ virtual ~FileDispatcher() {
+ ss_->Remove(this);
+ }
+
+ SocketServer* socketserver() { return ss_; }
+
+ virtual int GetDescriptor() {
+ return fd_;
+ }
+
+ virtual bool IsDescriptorClosed() {
+ return false;
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return flags_;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ if ((ff & DE_READ) != 0)
+ SignalReadEvent(this);
+ if ((ff & DE_WRITE) != 0)
+ SignalWriteEvent(this);
+ if ((ff & DE_CLOSE) != 0)
+ SignalCloseEvent(this, err);
+ }
+
+ virtual bool readable() {
+ return (flags_ & DE_READ) != 0;
+ }
+
+ virtual void set_readable(bool value) {
+ flags_ = value ? (flags_ | DE_READ) : (flags_ & ~DE_READ);
+ }
+
+ virtual bool writable() {
+ return (flags_ & DE_WRITE) != 0;
+ }
+
+ virtual void set_writable(bool value) {
+ flags_ = value ? (flags_ | DE_WRITE) : (flags_ & ~DE_WRITE);
+ }
+
+ private:
+ PhysicalSocketServer* ss_;
+ int fd_;
+ int flags_;
+};
+
+AsyncFile* PhysicalSocketServer::CreateFile(int fd) {
+ return new FileDispatcher(fd, this);
+}
+
+#endif // POSIX
+
+#ifdef WIN32
+static uint32 FlagsToEvents(uint32 events) {
+ uint32 ffFD = FD_CLOSE;
+ if (events & DE_READ)
+ ffFD |= FD_READ;
+ if (events & DE_WRITE)
+ ffFD |= FD_WRITE;
+ if (events & DE_CONNECT)
+ ffFD |= FD_CONNECT;
+ if (events & DE_ACCEPT)
+ ffFD |= FD_ACCEPT;
+ return ffFD;
+}
+
+class EventDispatcher : public Dispatcher {
+ public:
+ EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) {
+ hev_ = WSACreateEvent();
+ if (hev_) {
+ ss_->Add(this);
+ }
+ }
+
+ ~EventDispatcher() {
+ if (hev_ != NULL) {
+ ss_->Remove(this);
+ WSACloseEvent(hev_);
+ hev_ = NULL;
+ }
+ }
+
+ virtual void Signal() {
+ if (hev_ != NULL)
+ WSASetEvent(hev_);
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return 0;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ WSAResetEvent(hev_);
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ }
+
+ virtual WSAEVENT GetWSAEvent() {
+ return hev_;
+ }
+
+ virtual SOCKET GetSocket() {
+ return INVALID_SOCKET;
+ }
+
+ virtual bool CheckSignalClose() { return false; }
+
+private:
+ PhysicalSocketServer* ss_;
+ WSAEVENT hev_;
+};
+
+class SocketDispatcher : public Dispatcher, public PhysicalSocket {
+ public:
+ static int next_id_;
+ int id_;
+ bool signal_close_;
+ int signal_err_;
+
+ SocketDispatcher(PhysicalSocketServer* ss)
+ : PhysicalSocket(ss),
+ id_(0),
+ signal_close_(false) {
+ }
+
+ SocketDispatcher(SOCKET s, PhysicalSocketServer* ss)
+ : PhysicalSocket(ss, s),
+ id_(0),
+ signal_close_(false) {
+ }
+
+ virtual ~SocketDispatcher() {
+ Close();
+ }
+
+ bool Initialize() {
+ ASSERT(s_ != INVALID_SOCKET);
+ // Must be a non-blocking
+ u_long argp = 1;
+ ioctlsocket(s_, FIONBIO, &argp);
+ ss_->Add(this);
+ return true;
+ }
+
+ virtual bool Create(int type) {
+ // Create socket
+ if (!PhysicalSocket::Create(type))
+ return false;
+
+ if (!Initialize())
+ return false;
+
+ do { id_ = ++next_id_; } while (id_ == 0);
+ return true;
+ }
+
+ virtual int Close() {
+ if (s_ == INVALID_SOCKET)
+ return 0;
+
+ id_ = 0;
+ signal_close_ = false;
+ ss_->Remove(this);
+ return PhysicalSocket::Close();
+ }
+
+ virtual uint32 GetRequestedEvents() {
+ return enabled_events_;
+ }
+
+ virtual void OnPreEvent(uint32 ff) {
+ if ((ff & DE_CONNECT) != 0)
+ state_ = CS_CONNECTED;
+ // We set CS_CLOSED from CheckSignalClose.
+ }
+
+ virtual void OnEvent(uint32 ff, int err) {
+ int cache_id = id_;
+ if ((ff & DE_READ) != 0) {
+ enabled_events_ &= ~DE_READ;
+ SignalReadEvent(this);
+ }
+ if (((ff & DE_WRITE) != 0) && (id_ == cache_id)) {
+ enabled_events_ &= ~DE_WRITE;
+ SignalWriteEvent(this);
+ }
+ if (((ff & DE_CONNECT) != 0) && (id_ == cache_id)) {
+ if (ff != DE_CONNECT)
+ LOG(LS_VERBOSE) << "Signalled with DE_CONNECT: " << ff;
+ enabled_events_ &= ~DE_CONNECT;
+#ifdef _DEBUG
+ dbg_addr_ = "Connected @ ";
+ dbg_addr_.append(GetRemoteAddress().ToString());
+#endif // _DEBUG
+ SignalConnectEvent(this);
+ }
+ if (((ff & DE_ACCEPT) != 0) && (id_ == cache_id)) {
+ enabled_events_ &= ~DE_ACCEPT;
+ SignalReadEvent(this);
+ }
+ if (((ff & DE_CLOSE) != 0) && (id_ == cache_id)) {
+ signal_close_ = true;
+ signal_err_ = err;
+ }
+ }
+
+ virtual WSAEVENT GetWSAEvent() {
+ return WSA_INVALID_EVENT;
+ }
+
+ virtual SOCKET GetSocket() {
+ return s_;
+ }
+
+ virtual bool CheckSignalClose() {
+ if (!signal_close_)
+ return false;
+
+ char ch;
+ if (recv(s_, &ch, 1, MSG_PEEK) > 0)
+ return false;
+
+ state_ = CS_CLOSED;
+ signal_close_ = false;
+ SignalCloseEvent(this, signal_err_);
+ return true;
+ }
+};
+
+int SocketDispatcher::next_id_ = 0;
+
+#endif // WIN32
+
+// Sets the value of a boolean value to false when signaled.
+class Signaler : public EventDispatcher {
+ public:
+ Signaler(PhysicalSocketServer* ss, bool* pf)
+ : EventDispatcher(ss), pf_(pf) {
+ }
+ virtual ~Signaler() { }
+
+ void OnEvent(uint32 ff, int err) {
+ if (pf_)
+ *pf_ = false;
+ }
+
+ private:
+ bool *pf_;
+};
+
+PhysicalSocketServer::PhysicalSocketServer()
+ : fWait_(false),
+ last_tick_tracked_(0),
+ last_tick_dispatch_count_(0) {
+ signal_wakeup_ = new Signaler(this, &fWait_);
+#ifdef WIN32
+ socket_ev_ = WSACreateEvent();
+#endif
+}
+
+PhysicalSocketServer::~PhysicalSocketServer() {
+#ifdef WIN32
+ WSACloseEvent(socket_ev_);
+#endif
+#ifdef POSIX
+ signal_dispatcher_.reset();
+#endif
+ delete signal_wakeup_;
+ ASSERT(dispatchers_.empty());
+}
+
+void PhysicalSocketServer::WakeUp() {
+ signal_wakeup_->Signal();
+}
+
+Socket* PhysicalSocketServer::CreateSocket(int type) {
+ PhysicalSocket* socket = new PhysicalSocket(this);
+ if (socket->Create(type)) {
+ return socket;
+ } else {
+ delete socket;
+ return 0;
+ }
+}
+
+AsyncSocket* PhysicalSocketServer::CreateAsyncSocket(int type) {
+ SocketDispatcher* dispatcher = new SocketDispatcher(this);
+ if (dispatcher->Create(type)) {
+ return dispatcher;
+ } else {
+ delete dispatcher;
+ return 0;
+ }
+}
+
+AsyncSocket* PhysicalSocketServer::WrapSocket(SOCKET s) {
+ SocketDispatcher* dispatcher = new SocketDispatcher(s, this);
+ if (dispatcher->Initialize()) {
+ return dispatcher;
+ } else {
+ delete dispatcher;
+ return 0;
+ }
+}
+
+void PhysicalSocketServer::Add(Dispatcher *pdispatcher) {
+ CritScope cs(&crit_);
+ // Prevent duplicates. This can cause dead dispatchers to stick around.
+ DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+ dispatchers_.end(),
+ pdispatcher);
+ if (pos != dispatchers_.end())
+ return;
+ dispatchers_.push_back(pdispatcher);
+}
+
+void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) {
+ CritScope cs(&crit_);
+ DispatcherList::iterator pos = std::find(dispatchers_.begin(),
+ dispatchers_.end(),
+ pdispatcher);
+ ASSERT(pos != dispatchers_.end());
+ size_t index = pos - dispatchers_.begin();
+ dispatchers_.erase(pos);
+ for (IteratorList::iterator it = iterators_.begin(); it != iterators_.end();
+ ++it) {
+ if (index < **it) {
+ --**it;
+ }
+ }
+}
+
+#ifdef POSIX
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+ // Calculate timing information
+
+ struct timeval *ptvWait = NULL;
+ struct timeval tvWait;
+ struct timeval tvStop;
+ if (cmsWait != kForever) {
+ // Calculate wait timeval
+ tvWait.tv_sec = cmsWait / 1000;
+ tvWait.tv_usec = (cmsWait % 1000) * 1000;
+ ptvWait = &tvWait;
+
+ // Calculate when to return in a timeval
+ gettimeofday(&tvStop, NULL);
+ tvStop.tv_sec += tvWait.tv_sec;
+ tvStop.tv_usec += tvWait.tv_usec;
+ if (tvStop.tv_usec >= 1000000) {
+ tvStop.tv_usec -= 1000000;
+ tvStop.tv_sec += 1;
+ }
+ }
+
+ // Zero all fd_sets. Don't need to do this inside the loop since
+ // select() zeros the descriptors not signaled
+
+ fd_set fdsRead;
+ FD_ZERO(&fdsRead);
+ fd_set fdsWrite;
+ FD_ZERO(&fdsWrite);
+
+ fWait_ = true;
+
+ while (fWait_) {
+ int fdmax = -1;
+ {
+ CritScope cr(&crit_);
+ for (size_t i = 0; i < dispatchers_.size(); ++i) {
+ // Query dispatchers for read and write wait state
+ Dispatcher *pdispatcher = dispatchers_[i];
+ ASSERT(pdispatcher);
+ if (!process_io && (pdispatcher != signal_wakeup_))
+ continue;
+ int fd = pdispatcher->GetDescriptor();
+ if (fd > fdmax)
+ fdmax = fd;
+
+ uint32 ff = pdispatcher->GetRequestedEvents();
+ if (ff & (DE_READ | DE_ACCEPT))
+ FD_SET(fd, &fdsRead);
+ if (ff & (DE_WRITE | DE_CONNECT))
+ FD_SET(fd, &fdsWrite);
+ }
+ }
+
+ // Wait then call handlers as appropriate
+ // < 0 means error
+ // 0 means timeout
+ // > 0 means count of descriptors ready
+ int n = select(fdmax + 1, &fdsRead, &fdsWrite, NULL, ptvWait);
+
+ // If error, return error.
+ if (n < 0) {
+ if (errno != EINTR) {
+ LOG_E(LS_ERROR, EN, errno) << "select";
+ return false;
+ }
+ // Else ignore the error and keep going. If this EINTR was for one of the
+ // signals managed by this PhysicalSocketServer, the
+ // PosixSignalDeliveryDispatcher will be in the signaled state in the next
+ // iteration.
+ } else if (n == 0) {
+ // If timeout, return success
+ return true;
+ } else {
+ // We have signaled descriptors
+ CritScope cr(&crit_);
+ for (size_t i = 0; i < dispatchers_.size(); ++i) {
+ Dispatcher *pdispatcher = dispatchers_[i];
+ int fd = pdispatcher->GetDescriptor();
+ uint32 ff = 0;
+ int errcode = 0;
+
+ // Reap any error code, which can be signaled through reads or writes.
+ // TODO: Should we set errcode if getsockopt fails?
+ if (FD_ISSET(fd, &fdsRead) || FD_ISSET(fd, &fdsWrite)) {
+ socklen_t len = sizeof(errcode);
+ ::getsockopt(fd, SOL_SOCKET, SO_ERROR, &errcode, &len);
+ }
+
+ // Check readable descriptors. If we're waiting on an accept, signal
+ // that. Otherwise we're waiting for data, check to see if we're
+ // readable or really closed.
+ // TODO: Only peek at TCP descriptors.
+ if (FD_ISSET(fd, &fdsRead)) {
+ FD_CLR(fd, &fdsRead);
+ if (pdispatcher->GetRequestedEvents() & DE_ACCEPT) {
+ ff |= DE_ACCEPT;
+ } else if (errcode || pdispatcher->IsDescriptorClosed()) {
+ ff |= DE_CLOSE;
+ } else {
+ ff |= DE_READ;
+ }
+ }
+
+ // Check writable descriptors. If we're waiting on a connect, detect
+ // success versus failure by the reaped error code.
+ if (FD_ISSET(fd, &fdsWrite)) {
+ FD_CLR(fd, &fdsWrite);
+ if (pdispatcher->GetRequestedEvents() & DE_CONNECT) {
+ if (!errcode) {
+ ff |= DE_CONNECT;
+ } else {
+ ff |= DE_CLOSE;
+ }
+ } else {
+ ff |= DE_WRITE;
+ }
+ }
+
+ // Tell the descriptor about the event.
+ if (ff != 0) {
+ pdispatcher->OnPreEvent(ff);
+ pdispatcher->OnEvent(ff, errcode);
+ }
+ }
+ }
+
+ // Recalc the time remaining to wait. Doing it here means it doesn't get
+ // calced twice the first time through the loop
+
+ if (cmsWait != kForever) {
+ ptvWait->tv_sec = 0;
+ ptvWait->tv_usec = 0;
+ struct timeval tvT;
+ gettimeofday(&tvT, NULL);
+ if ((tvStop.tv_sec > tvT.tv_sec)
+ || ((tvStop.tv_sec == tvT.tv_sec)
+ && (tvStop.tv_usec > tvT.tv_usec))) {
+ ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec;
+ ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec;
+ if (ptvWait->tv_usec < 0) {
+ ASSERT(ptvWait->tv_sec > 0);
+ ptvWait->tv_usec += 1000000;
+ ptvWait->tv_sec -= 1;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static void GlobalSignalHandler(int signum) {
+ PosixSignalHandler::Instance()->OnPosixSignalReceived(signum);
+}
+
+bool PhysicalSocketServer::SetPosixSignalHandler(int signum,
+ void (*handler)(int)) {
+ // If handler is SIG_IGN or SIG_DFL then clear our user-level handler,
+ // otherwise set one.
+ if (handler == SIG_IGN || handler == SIG_DFL) {
+ if (!InstallSignal(signum, handler)) {
+ return false;
+ }
+ if (signal_dispatcher_.get()) {
+ signal_dispatcher_->ClearHandler(signum);
+ if (!signal_dispatcher_->HasHandlers()) {
+ signal_dispatcher_.reset();
+ }
+ }
+ } else {
+ if (!signal_dispatcher_.get()) {
+ signal_dispatcher_.reset(new PosixSignalDispatcher(this));
+ }
+ signal_dispatcher_->SetHandler(signum, handler);
+ if (!InstallSignal(signum, &GlobalSignalHandler)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool PhysicalSocketServer::InstallSignal(int signum, void (*handler)(int)) {
+ struct sigaction act;
+ // It doesn't really matter what we set this mask to.
+ if (sigemptyset(&act.sa_mask) != 0) {
+ LOG_ERR(LS_ERROR) << "Couldn't set mask";
+ return false;
+ }
+ act.sa_handler = handler;
+ // Use SA_RESTART so that our syscalls don't get EINTR, since we don't need it
+ // and it's a nuisance. Though some syscalls still return EINTR and there's no
+ // real standard for which ones. :(
+ act.sa_flags = SA_RESTART;
+ if (sigaction(signum, &act, NULL) != 0) {
+ LOG_ERR(LS_ERROR) << "Couldn't set sigaction";
+ return false;
+ }
+ return true;
+}
+#endif // POSIX
+
+#ifdef WIN32
+bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) {
+ int cmsTotal = cmsWait;
+ int cmsElapsed = 0;
+ uint32 msStart = Time();
+
+#if LOGGING
+ if (last_tick_dispatch_count_ == 0) {
+ last_tick_tracked_ = msStart;
+ }
+#endif
+
+ fWait_ = true;
+ while (fWait_) {
+ std::vector<WSAEVENT> events;
+ std::vector<Dispatcher *> event_owners;
+
+ events.push_back(socket_ev_);
+
+ {
+ CritScope cr(&crit_);
+ size_t i = 0;
+ iterators_.push_back(&i);
+ // Don't track dispatchers_.size(), because we want to pick up any new
+ // dispatchers that were added while processing the loop.
+ while (i < dispatchers_.size()) {
+ Dispatcher* disp = dispatchers_[i++];
+ if (!process_io && (disp != signal_wakeup_))
+ continue;
+ SOCKET s = disp->GetSocket();
+ if (disp->CheckSignalClose()) {
+ // We just signalled close, don't poll this socket
+ } else if (s != INVALID_SOCKET) {
+ WSAEventSelect(s,
+ events[0],
+ FlagsToEvents(disp->GetRequestedEvents()));
+ } else {
+ events.push_back(disp->GetWSAEvent());
+ event_owners.push_back(disp);
+ }
+ }
+ ASSERT(iterators_.back() == &i);
+ iterators_.pop_back();
+ }
+
+ // Which is shorter, the delay wait or the asked wait?
+
+ int cmsNext;
+ if (cmsWait == kForever) {
+ cmsNext = cmsWait;
+ } else {
+ cmsNext = _max(0, cmsTotal - cmsElapsed);
+ }
+
+ // Wait for one of the events to signal
+ DWORD dw = WSAWaitForMultipleEvents(static_cast<DWORD>(events.size()),
+ &events[0],
+ false,
+ cmsNext,
+ false);
+
+#if 0 // LOGGING
+ // we track this information purely for logging purposes.
+ last_tick_dispatch_count_++;
+ if (last_tick_dispatch_count_ >= 1000) {
+ int32 elapsed = TimeSince(last_tick_tracked_);
+ LOG(INFO) << "PhysicalSocketServer took " << elapsed
+ << "ms for 1000 events";
+
+ // If we get more than 1000 events in a second, we are spinning badly
+ // (normally it should take about 8-20 seconds).
+ ASSERT(elapsed > 1000);
+
+ last_tick_tracked_ = Time();
+ last_tick_dispatch_count_ = 0;
+ }
+#endif
+
+ if (dw == WSA_WAIT_FAILED) {
+ // Failed?
+ // TODO: need a better strategy than this!
+ int error = WSAGetLastError();
+ ASSERT(false);
+ return false;
+ } else if (dw == WSA_WAIT_TIMEOUT) {
+ // Timeout?
+ return true;
+ } else {
+ // Figure out which one it is and call it
+ CritScope cr(&crit_);
+ int index = dw - WSA_WAIT_EVENT_0;
+ if (index > 0) {
+ --index; // The first event is the socket event
+ event_owners[index]->OnPreEvent(0);
+ event_owners[index]->OnEvent(0, 0);
+ } else if (process_io) {
+ size_t i = 0, end = dispatchers_.size();
+ iterators_.push_back(&i);
+ iterators_.push_back(&end); // Don't iterate over new dispatchers.
+ while (i < end) {
+ Dispatcher* disp = dispatchers_[i++];
+ SOCKET s = disp->GetSocket();
+ if (s == INVALID_SOCKET)
+ continue;
+
+ WSANETWORKEVENTS wsaEvents;
+ int err = WSAEnumNetworkEvents(s, events[0], &wsaEvents);
+ if (err == 0) {
+
+#if LOGGING
+ {
+ if ((wsaEvents.lNetworkEvents & FD_READ) &&
+ wsaEvents.iErrorCode[FD_READ_BIT] != 0) {
+ LOG(WARNING) << "PhysicalSocketServer got FD_READ_BIT error "
+ << wsaEvents.iErrorCode[FD_READ_BIT];
+ }
+ if ((wsaEvents.lNetworkEvents & FD_WRITE) &&
+ wsaEvents.iErrorCode[FD_WRITE_BIT] != 0) {
+ LOG(WARNING) << "PhysicalSocketServer got FD_WRITE_BIT error "
+ << wsaEvents.iErrorCode[FD_WRITE_BIT];
+ }
+ if ((wsaEvents.lNetworkEvents & FD_CONNECT) &&
+ wsaEvents.iErrorCode[FD_CONNECT_BIT] != 0) {
+ LOG(WARNING) << "PhysicalSocketServer got FD_CONNECT_BIT error "
+ << wsaEvents.iErrorCode[FD_CONNECT_BIT];
+ }
+ if ((wsaEvents.lNetworkEvents & FD_ACCEPT) &&
+ wsaEvents.iErrorCode[FD_ACCEPT_BIT] != 0) {
+ LOG(WARNING) << "PhysicalSocketServer got FD_ACCEPT_BIT error "
+ << wsaEvents.iErrorCode[FD_ACCEPT_BIT];
+ }
+ if ((wsaEvents.lNetworkEvents & FD_CLOSE) &&
+ wsaEvents.iErrorCode[FD_CLOSE_BIT] != 0) {
+ LOG(WARNING) << "PhysicalSocketServer got FD_CLOSE_BIT error "
+ << wsaEvents.iErrorCode[FD_CLOSE_BIT];
+ }
+ }
+#endif
+ uint32 ff = 0;
+ int errcode = 0;
+ if (wsaEvents.lNetworkEvents & FD_READ)
+ ff |= DE_READ;
+ if (wsaEvents.lNetworkEvents & FD_WRITE)
+ ff |= DE_WRITE;
+ if (wsaEvents.lNetworkEvents & FD_CONNECT) {
+ if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) {
+ ff |= DE_CONNECT;
+ } else {
+ ff |= DE_CLOSE;
+ errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT];
+ }
+ }
+ if (wsaEvents.lNetworkEvents & FD_ACCEPT)
+ ff |= DE_ACCEPT;
+ if (wsaEvents.lNetworkEvents & FD_CLOSE) {
+ ff |= DE_CLOSE;
+ errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT];
+ }
+ if (ff != 0) {
+ disp->OnPreEvent(ff);
+ disp->OnEvent(ff, errcode);
+ }
+ }
+ }
+ ASSERT(iterators_.back() == &end);
+ iterators_.pop_back();
+ ASSERT(iterators_.back() == &i);
+ iterators_.pop_back();
+ }
+
+ // Reset the network event until new activity occurs
+ WSAResetEvent(socket_ev_);
+ }
+
+ // Break?
+ if (!fWait_)
+ break;
+ cmsElapsed = TimeSince(msStart);
+ if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) {
+ break;
+ }
+ }
+
+ // Done
+ return true;
+}
+#endif // WIN32
+
+} // namespace talk_base
diff --git a/talk/base/physicalsocketserver.h b/talk/base/physicalsocketserver.h
new file mode 100644
index 0000000..aeb8348
--- /dev/null
+++ b/talk/base/physicalsocketserver.h
@@ -0,0 +1,133 @@
+/*
+ * 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_PHYSICALSOCKETSERVER_H__
+#define TALK_BASE_PHYSICALSOCKETSERVER_H__
+
+#include <vector>
+
+#include "talk/base/asyncfile.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/criticalsection.h"
+
+#ifdef POSIX
+typedef int SOCKET;
+#endif // POSIX
+
+namespace talk_base {
+
+// Event constants for the Dispatcher class.
+enum DispatcherEvent {
+ DE_READ = 0x0001,
+ DE_WRITE = 0x0002,
+ DE_CONNECT = 0x0004,
+ DE_CLOSE = 0x0008,
+ DE_ACCEPT = 0x0010,
+};
+
+class Signaler;
+#ifdef POSIX
+class PosixSignalDispatcher;
+#endif
+
+class Dispatcher {
+ public:
+ virtual ~Dispatcher() {}
+ virtual uint32 GetRequestedEvents() = 0;
+ virtual void OnPreEvent(uint32 ff) = 0;
+ virtual void OnEvent(uint32 ff, int err) = 0;
+#ifdef WIN32
+ virtual WSAEVENT GetWSAEvent() = 0;
+ virtual SOCKET GetSocket() = 0;
+ virtual bool CheckSignalClose() = 0;
+#elif POSIX
+ virtual int GetDescriptor() = 0;
+ virtual bool IsDescriptorClosed() = 0;
+#endif
+};
+
+// A socket server that provides the real sockets of the underlying OS.
+class PhysicalSocketServer : public SocketServer {
+public:
+ PhysicalSocketServer();
+ virtual ~PhysicalSocketServer();
+
+ // SocketFactory:
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+
+ // Internal Factory for Accept
+ AsyncSocket* WrapSocket(SOCKET s);
+
+ // SocketServer:
+ virtual bool Wait(int cms, bool process_io);
+ virtual void WakeUp();
+
+ void Add(Dispatcher* dispatcher);
+ void Remove(Dispatcher* dispatcher);
+
+#ifdef POSIX
+ AsyncFile* CreateFile(int fd);
+
+ // Sets the function to be executed in response to the specified POSIX signal.
+ // The function is executed from inside Wait() using the "self-pipe trick"--
+ // regardless of which thread receives the signal--and hence can safely
+ // manipulate user-level data structures.
+ // "handler" may be SIG_IGN, SIG_DFL, or a user-specified function, just like
+ // with signal(2).
+ // Only one PhysicalSocketServer should have user-level signal handlers.
+ // Dispatching signals on multiple PhysicalSocketServers is not reliable.
+ // The signal mask is not modified. It is the caller's responsibily to
+ // maintain it as desired.
+ bool SetPosixSignalHandler(int signum, void (*handler)(int));
+#endif
+
+private:
+ typedef std::vector<Dispatcher*> DispatcherList;
+ typedef std::vector<size_t*> IteratorList;
+
+#ifdef POSIX
+ static bool InstallSignal(int signum, void (*handler)(int));
+
+ scoped_ptr<PosixSignalDispatcher> signal_dispatcher_;
+#endif
+ DispatcherList dispatchers_;
+ IteratorList iterators_;
+ Signaler* signal_wakeup_;
+ CriticalSection crit_;
+ bool fWait_;
+ uint32 last_tick_tracked_;
+ int last_tick_dispatch_count_;
+#ifdef WIN32
+ WSAEVENT socket_ev_;
+#endif
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PHYSICALSOCKETSERVER_H__
diff --git a/talk/base/proxydetect.cc b/talk/base/proxydetect.cc
new file mode 100644
index 0000000..66213ee
--- /dev/null
+++ b/talk/base/proxydetect.cc
@@ -0,0 +1,1259 @@
+/*
+ * 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 "talk/base/proxydetect.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#include <shlobj.h>
+#endif // WIN32
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef OSX
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <Security/Security.h>
+#include "macconversion.h"
+#endif
+
+#include <map>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#define _TRY_WINHTTP 1
+#define _TRY_JSPROXY 0
+#define _TRY_WM_FINDPROXY 0
+#define _TRY_IE_LAN_SETTINGS 1
+#endif // WIN32
+
+// For all platforms try Firefox.
+#define _TRY_FIREFOX 1
+
+// Use profiles.ini to find the correct profile for this user.
+// If not set, we'll just look for the default one.
+#define USE_FIREFOX_PROFILES_INI 1
+
+static const size_t kMaxLineLength = 1024;
+static const char kFirefoxPattern[] = "Firefox";
+static const char kInternetExplorerPattern[] = "MSIE";
+
+struct StringMap {
+ public:
+ void Add(const char * name, const char * value) { map_[name] = value; }
+ const std::string& Get(const char * name, const char * def = "") const {
+ std::map<std::string, std::string>::const_iterator it =
+ map_.find(name);
+ if (it != map_.end())
+ return it->second;
+ def_ = def;
+ return def_;
+ }
+ bool IsSet(const char * name) const {
+ return (map_.find(name) != map_.end());
+ }
+ private:
+ std::map<std::string, std::string> map_;
+ mutable std::string def_;
+};
+
+enum UserAgent {
+ UA_FIREFOX,
+ UA_INTERNETEXPLORER,
+ UA_OTHER,
+ UA_UNKNOWN
+};
+
+#if _TRY_WINHTTP
+//#include <winhttp.h>
+// Note: From winhttp.h
+
+const char WINHTTP[] = "winhttp";
+
+typedef LPVOID HINTERNET;
+
+typedef struct {
+ DWORD dwAccessType; // see WINHTTP_ACCESS_* types below
+ LPWSTR lpszProxy; // proxy server list
+ LPWSTR lpszProxyBypass; // proxy bypass list
+} WINHTTP_PROXY_INFO, * LPWINHTTP_PROXY_INFO;
+
+typedef struct {
+ DWORD dwFlags;
+ DWORD dwAutoDetectFlags;
+ LPCWSTR lpszAutoConfigUrl;
+ LPVOID lpvReserved;
+ DWORD dwReserved;
+ BOOL fAutoLogonIfChallenged;
+} WINHTTP_AUTOPROXY_OPTIONS;
+
+typedef struct {
+ BOOL fAutoDetect;
+ LPWSTR lpszAutoConfigUrl;
+ LPWSTR lpszProxy;
+ LPWSTR lpszProxyBypass;
+} WINHTTP_CURRENT_USER_IE_PROXY_CONFIG;
+
+extern "C" {
+ typedef HINTERNET (WINAPI * pfnWinHttpOpen)
+ (
+ IN LPCWSTR pwszUserAgent,
+ IN DWORD dwAccessType,
+ IN LPCWSTR pwszProxyName OPTIONAL,
+ IN LPCWSTR pwszProxyBypass OPTIONAL,
+ IN DWORD dwFlags
+ );
+ typedef BOOL (STDAPICALLTYPE * pfnWinHttpCloseHandle)
+ (
+ IN HINTERNET hInternet
+ );
+ typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetProxyForUrl)
+ (
+ IN HINTERNET hSession,
+ IN LPCWSTR lpcwszUrl,
+ IN WINHTTP_AUTOPROXY_OPTIONS * pAutoProxyOptions,
+ OUT WINHTTP_PROXY_INFO * pProxyInfo
+ );
+ typedef BOOL (STDAPICALLTYPE * pfnWinHttpGetIEProxyConfig)
+ (
+ IN OUT WINHTTP_CURRENT_USER_IE_PROXY_CONFIG * pProxyConfig
+ );
+
+} // extern "C"
+
+#define WINHTTP_AUTOPROXY_AUTO_DETECT 0x00000001
+#define WINHTTP_AUTOPROXY_CONFIG_URL 0x00000002
+#define WINHTTP_AUTOPROXY_RUN_INPROCESS 0x00010000
+#define WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY 0x00020000
+#define WINHTTP_AUTO_DETECT_TYPE_DHCP 0x00000001
+#define WINHTTP_AUTO_DETECT_TYPE_DNS_A 0x00000002
+#define WINHTTP_ACCESS_TYPE_DEFAULT_PROXY 0
+#define WINHTTP_ACCESS_TYPE_NO_PROXY 1
+#define WINHTTP_ACCESS_TYPE_NAMED_PROXY 3
+#define WINHTTP_NO_PROXY_NAME NULL
+#define WINHTTP_NO_PROXY_BYPASS NULL
+
+#endif // _TRY_WINHTTP
+
+#if _TRY_JSPROXY
+extern "C" {
+ typedef BOOL (STDAPICALLTYPE * pfnInternetGetProxyInfo)
+ (
+ LPCSTR lpszUrl,
+ DWORD dwUrlLength,
+ LPSTR lpszUrlHostName,
+ DWORD dwUrlHostNameLength,
+ LPSTR * lplpszProxyHostName,
+ LPDWORD lpdwProxyHostNameLength
+ );
+} // extern "C"
+#endif // _TRY_JSPROXY
+
+#if _TRY_WM_FINDPROXY
+#include <comutil.h>
+#include <wmnetsourcecreator.h>
+#include <wmsinternaladminnetsource.h>
+#endif // _TRY_WM_FINDPROXY
+
+#if _TRY_IE_LAN_SETTINGS
+#include <wininet.h>
+#include <string>
+#endif // _TRY_IE_LAN_SETTINGS
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Utility Functions
+//////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+#ifdef _UNICODE
+
+typedef std::wstring tstring;
+std::string Utf8String(const tstring& str) { return ToUtf8(str); }
+
+#else // !_UNICODE
+
+typedef std::string tstring;
+std::string Utf8String(const tstring& str) { return str; }
+
+#endif // !_UNICODE
+#endif // WIN32
+
+bool ProxyItemMatch(const Url<char>& url, char * item, size_t len) {
+ // hostname:443
+ if (char * port = ::strchr(item, ':')) {
+ *port++ = '\0';
+ if (url.port() != atol(port)) {
+ return false;
+ }
+ }
+
+ // A.B.C.D or A.B.C.D/24
+ int a, b, c, d, m;
+ int match = sscanf(item, "%d.%d.%d.%d/%d", &a, &b, &c, &d, &m);
+ if (match >= 4) {
+ uint32 ip = ((a & 0xFF) << 24) | ((b & 0xFF) << 16) | ((c & 0xFF) << 8) |
+ (d & 0xFF);
+ if ((match < 5) || (m > 32))
+ m = 32;
+ else if (m < 0)
+ m = 0;
+ uint32 mask = (m == 0) ? 0 : (~0UL) << (32 - m);
+ SocketAddress addr(url.host(), 0);
+ return !addr.IsUnresolved() && ((addr.ip() & mask) == (ip & mask));
+ }
+
+ // .foo.com
+ if (*item == '.') {
+ size_t hostlen = url.host().length();
+ return (hostlen > len)
+ && (stricmp(url.host().c_str() + (hostlen - len), item) == 0);
+ }
+
+ // localhost or www.*.com
+ if (!string_match(url.host().c_str(), item))
+ return false;
+
+ return true;
+}
+
+bool ProxyListMatch(const Url<char>& url, const std::string& proxy_list,
+ char sep) {
+ const size_t BUFSIZE = 256;
+ char buffer[BUFSIZE];
+ const char* list = proxy_list.c_str();
+ while (*list) {
+ // Remove leading space
+ if (isspace(*list)) {
+ ++list;
+ continue;
+ }
+ // Break on separator
+ size_t len;
+ const char * start = list;
+ if (const char * end = ::strchr(list, sep)) {
+ len = (end - list);
+ list += len + 1;
+ } else {
+ len = strlen(list);
+ list += len;
+ }
+ // Remove trailing space
+ while ((len > 0) && isspace(start[len-1]))
+ --len;
+ // Check for oversized entry
+ if (len >= BUFSIZE)
+ continue;
+ memcpy(buffer, start, len);
+ buffer[len] = 0;
+ if (!ProxyItemMatch(url, buffer, len))
+ continue;
+ return true;
+ }
+ return false;
+}
+
+bool Better(ProxyType lhs, const ProxyType rhs) {
+ // PROXY_NONE, PROXY_HTTPS, PROXY_SOCKS5, PROXY_UNKNOWN
+ const int PROXY_VALUE[5] = { 0, 2, 3, 1 };
+ return (PROXY_VALUE[lhs] > PROXY_VALUE[rhs]);
+}
+
+bool ParseProxy(const std::string& saddress, ProxyInfo* proxy) {
+ const size_t kMaxAddressLength = 1024;
+ // Allow semicolon, space, or tab as an address separator
+ const char* const kAddressSeparator = " ;\t";
+
+ ProxyType ptype;
+ std::string host;
+ uint16 port;
+
+ const char* address = saddress.c_str();
+ while (*address) {
+ size_t len;
+ const char * start = address;
+ if (const char * sep = strchr(address, kAddressSeparator)) {
+ len = (sep - address);
+ address += len + 1;
+ while (*address != '\0' && ::strchr(kAddressSeparator, *address)) {
+ address += 1;
+ }
+ } else {
+ len = strlen(address);
+ address += len;
+ }
+
+ if (len > kMaxAddressLength - 1) {
+ LOG(LS_WARNING) << "Proxy address too long [" << start << "]";
+ continue;
+ }
+
+ char buffer[kMaxAddressLength];
+ memcpy(buffer, start, len);
+ buffer[len] = 0;
+
+ char * colon = ::strchr(buffer, ':');
+ if (!colon) {
+ LOG(LS_WARNING) << "Proxy address without port [" << buffer << "]";
+ continue;
+ }
+
+ *colon = 0;
+ char * endptr;
+ port = static_cast<uint16>(strtol(colon + 1, &endptr, 0));
+ if (*endptr != 0) {
+ LOG(LS_WARNING) << "Proxy address with invalid port [" << buffer << "]";
+ continue;
+ }
+
+ if (char * equals = ::strchr(buffer, '=')) {
+ *equals = 0;
+ host = equals + 1;
+ if (_stricmp(buffer, "socks") == 0) {
+ ptype = PROXY_SOCKS5;
+ } else if (_stricmp(buffer, "https") == 0) {
+ ptype = PROXY_HTTPS;
+ } else {
+ LOG(LS_WARNING) << "Proxy address with unknown protocol ["
+ << buffer << "]";
+ ptype = PROXY_UNKNOWN;
+ }
+ } else {
+ host = buffer;
+ ptype = PROXY_UNKNOWN;
+ }
+
+ if (Better(ptype, proxy->type)) {
+ proxy->type = ptype;
+ proxy->address.SetIP(host);
+ proxy->address.SetPort(port);
+ }
+ }
+
+ return proxy->type != PROXY_NONE;
+}
+
+UserAgent GetAgent(const char* agent) {
+ if (agent) {
+ std::string agent_str(agent);
+ if (agent_str.find(kFirefoxPattern) != std::string::npos) {
+ return UA_FIREFOX;
+ } else if (agent_str.find(kInternetExplorerPattern) != std::string::npos) {
+ return UA_INTERNETEXPLORER;
+ } else if (agent_str.empty()) {
+ return UA_UNKNOWN;
+ }
+ }
+ return UA_OTHER;
+}
+
+bool EndsWith(const std::string& a, const std::string& b) {
+ if (b.size() > a.size()) {
+ return false;
+ }
+ int result = a.compare(a.size() - b.size(), b.size(), b);
+ return result == 0;
+}
+
+bool GetFirefoxProfilePath(Pathname* path) {
+#ifdef WIN32
+ wchar_t w_path[MAX_PATH];
+ if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, w_path) !=
+ S_OK) {
+ LOG(LS_ERROR) << "SHGetFolderPath failed";
+ return false;
+ }
+ path->SetFolder(ToUtf8(w_path, wcslen(w_path)));
+ path->AppendFolder("Mozilla");
+ path->AppendFolder("Firefox");
+#elif OSX
+ FSRef fr;
+ if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+ kCreateFolder, &fr)) {
+ LOG(LS_ERROR) << "FSFindFolder failed";
+ return false;
+ }
+ char buffer[NAME_MAX + 1];
+ if (0 != FSRefMakePath(&fr, reinterpret_cast<uint8*>(buffer),
+ ARRAY_SIZE(buffer))) {
+ LOG(LS_ERROR) << "FSRefMakePath failed";
+ return false;
+ }
+ path->SetFolder(std::string(buffer));
+ path->AppendFolder("Firefox");
+#else
+ char* user_home = getenv("HOME");
+ if (user_home == NULL) {
+ return false;
+ }
+ path->SetFolder(std::string(user_home));
+ path->AppendFolder(".mozilla");
+ path->AppendFolder("firefox");
+#endif // WIN32
+ return true;
+}
+
+bool GetDefaultFirefoxProfile(Pathname* profile_path) {
+ ASSERT(NULL != profile_path);
+ Pathname path;
+ if (!GetFirefoxProfilePath(&path)) {
+ return false;
+ }
+
+#if USE_FIREFOX_PROFILES_INI
+ // [Profile0]
+ // Name=default
+ // IsRelative=1
+ // Path=Profiles/2de53ejb.default
+ // Default=1
+
+ // Note: we are looking for the first entry with "Default=1", or the last
+ // entry in the file
+ path.SetFilename("profiles.ini");
+ FileStream* fs = Filesystem::OpenFile(path, "r");
+ if (!fs) {
+ return false;
+ }
+ Pathname candidate;
+ bool relative = true;
+ std::string line;
+ while (fs->ReadLine(&line) == SR_SUCCESS) {
+ if (line.length() == 0) {
+ continue;
+ }
+ if (line.at(0) == '[') {
+ relative = true;
+ candidate.clear();
+ } else if (line.find("IsRelative=") == 0 &&
+ line.length() >= 12) {
+ // TODO: The initial Linux public launch revealed a fairly
+ // high number of machines where IsRelative= did not have anything after
+ // it. Perhaps that is legal profiles.ini syntax?
+ relative = (line.at(11) != '0');
+ } else if (line.find("Path=") == 0 &&
+ line.length() >= 6) {
+ if (relative) {
+ candidate = path;
+ } else {
+ candidate.clear();
+ }
+ candidate.AppendFolder(line.substr(5));
+ } else if (line.find("Default=") == 0 &&
+ line.length() >= 9) {
+ if ((line.at(8) != '0') && !candidate.empty()) {
+ break;
+ }
+ }
+ }
+ fs->Close();
+ if (candidate.empty()) {
+ return false;
+ }
+ profile_path->SetPathname(candidate.pathname());
+
+#else // !USE_FIREFOX_PROFILES_INI
+ path.AppendFolder("Profiles");
+ DirectoryIterator* it = Filesystem::IterateDirectory();
+ it->Iterate(path);
+ std::string extension(".default");
+ while (!EndsWith(it->Name(), extension)) {
+ if (!it->Next()) {
+ return false;
+ }
+ }
+
+ profile_path->SetPathname(path);
+ profile->AppendFolder("Profiles");
+ profile->AppendFolder(it->Name());
+ delete it;
+
+#endif // !USE_FIREFOX_PROFILES_INI
+
+ return true;
+}
+
+bool ReadFirefoxPrefs(const Pathname& filename,
+ const char * prefix,
+ StringMap* settings) {
+ FileStream* fs = Filesystem::OpenFile(filename, "r");
+ if (!fs) {
+ LOG(LS_ERROR) << "Failed to open file: " << filename.pathname();
+ return false;
+ }
+
+ std::string line;
+ while (fs->ReadLine(&line) == SR_SUCCESS) {
+ size_t prefix_len = strlen(prefix);
+
+ // Skip blank lines and too long lines.
+ if ((line.length() == 0) || (line.length() > kMaxLineLength)
+ || (line.at(0) == '#') || line.compare(0, 2, "/*") == 0
+ || line.compare(0, 2, " *") == 0) {
+ continue;
+ }
+
+ char buffer[kMaxLineLength];
+ strcpyn(buffer, sizeof(buffer), line.c_str());
+ int nstart = 0, nend = 0, vstart = 0, vend = 0;
+ sscanf(buffer, "user_pref(\"%n%*[^\"]%n\", %n%*[^)]%n);",
+ &nstart, &nend, &vstart, &vend);
+ if (vend > 0) {
+ char* name = buffer + nstart;
+ name[nend - nstart] = 0;
+ if ((vend - vstart >= 2) && (buffer[vstart] == '"')) {
+ vstart += 1;
+ vend -= 1;
+ }
+ char* value = buffer + vstart;
+ value[vend - vstart] = 0;
+ if ((strncmp(name, prefix, prefix_len) == 0) && *value) {
+ settings->Add(name + prefix_len, value);
+ }
+ } else {
+ LOG_F(LS_WARNING) << "Unparsed pref [" << buffer << "]";
+ }
+ }
+ fs->Close();
+ return true;
+}
+
+bool GetFirefoxProxySettings(const char* url, ProxyInfo* proxy) {
+ Url<char> purl(url);
+ Pathname path;
+ bool success = false;
+ if (GetDefaultFirefoxProfile(&path)) {
+ StringMap settings;
+ path.SetFilename("prefs.js");
+ if (ReadFirefoxPrefs(path, "network.proxy.", &settings)) {
+ success = true;
+ proxy->bypass_list =
+ settings.Get("no_proxies_on", "localhost, 127.0.0.1");
+ if (settings.Get("type") == "1") {
+ // User has manually specified a proxy, try to figure out what
+ // type it is.
+ if (ProxyListMatch(purl, proxy->bypass_list.c_str(), ',')) {
+ // Our url is in the list of url's to bypass proxy.
+ } else if (settings.Get("share_proxy_settings") == "true") {
+ proxy->type = PROXY_UNKNOWN;
+ proxy->address.SetIP(settings.Get("http"));
+ proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+ } else if (settings.IsSet("socks")) {
+ proxy->type = PROXY_SOCKS5;
+ proxy->address.SetIP(settings.Get("socks"));
+ proxy->address.SetPort(atoi(settings.Get("socks_port").c_str()));
+ } else if (settings.IsSet("ssl")) {
+ proxy->type = PROXY_HTTPS;
+ proxy->address.SetIP(settings.Get("ssl"));
+ proxy->address.SetPort(atoi(settings.Get("ssl_port").c_str()));
+ } else if (settings.IsSet("http")) {
+ proxy->type = PROXY_HTTPS;
+ proxy->address.SetIP(settings.Get("http"));
+ proxy->address.SetPort(atoi(settings.Get("http_port").c_str()));
+ }
+ } else if (settings.Get("type") == "2") {
+ // Browser is configured to get proxy settings from a given url.
+ proxy->autoconfig_url = settings.Get("autoconfig_url").c_str();
+ } else if (settings.Get("type") == "4") {
+ // Browser is configured to auto detect proxy config.
+ proxy->autodetect = true;
+ } else {
+ // No proxy set.
+ }
+ }
+ }
+ return success;
+}
+
+#ifdef WIN32 // Windows specific implementation for reading Internet
+ // Explorer proxy settings.
+
+void LogGetProxyFault() {
+ LOG_GLEM(LERROR, WINHTTP) << "WinHttpGetProxyForUrl faulted!!";
+}
+
+BOOL MyWinHttpGetProxyForUrl(pfnWinHttpGetProxyForUrl pWHGPFU,
+ HINTERNET hWinHttp, LPCWSTR url,
+ WINHTTP_AUTOPROXY_OPTIONS *options,
+ WINHTTP_PROXY_INFO *info) {
+ // WinHttpGetProxyForUrl() can call plugins which can crash.
+ // In the case of McAfee scriptproxy.dll, it does crash in
+ // older versions. Try to catch crashes here and treat as an
+ // error.
+ BOOL success = FALSE;
+
+#if (_HAS_EXCEPTIONS == 0)
+ __try {
+ success = pWHGPFU(hWinHttp, url, options, info);
+ } __except(EXCEPTION_EXECUTE_HANDLER) {
+ // This is a separate function to avoid
+ // Visual C++ error 2712 when compiling with C++ EH
+ LogGetProxyFault();
+ }
+#else
+ success = pWHGPFU(hWinHttp, url, options, info);
+#endif // (_HAS_EXCEPTIONS == 0)
+
+ return success;
+}
+
+bool IsDefaultBrowserFirefox() {
+ HKEY key;
+ LONG result = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"http\\shell\\open\\command",
+ 0, KEY_READ, &key);
+ if (ERROR_SUCCESS != result)
+ return false;
+
+ wchar_t* value = NULL;
+ DWORD size, type;
+ result = RegQueryValueEx(key, L"", 0, &type, NULL, &size);
+ if (REG_SZ != type) {
+ result = ERROR_ACCESS_DENIED; // Any error is fine
+ } else if (ERROR_SUCCESS == result) {
+ value = new wchar_t[size+1];
+ BYTE* buffer = reinterpret_cast<BYTE*>(value);
+ result = RegQueryValueEx(key, L"", 0, &type, buffer, &size);
+ }
+ RegCloseKey(key);
+
+ bool success = false;
+ if (ERROR_SUCCESS == result) {
+ value[size] = L'\0';
+ for (size_t i = 0; i < size; ++i) {
+ value[i] = tolowercase(value[i]);
+ }
+ success = (NULL != strstr(value, L"firefox.exe"));
+ }
+ delete [] value;
+ return success;
+}
+
+bool GetWinHttpProxySettings(const char* url, ProxyInfo* proxy) {
+ HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+ if (winhttp_handle == NULL) {
+ LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+ return false;
+ }
+ WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg;
+ memset(&iecfg, 0, sizeof(iecfg));
+ Url<char> purl(url);
+ pfnWinHttpGetIEProxyConfig pWHGIEPC =
+ reinterpret_cast<pfnWinHttpGetIEProxyConfig>(
+ GetProcAddress(winhttp_handle,
+ "WinHttpGetIEProxyConfigForCurrentUser"));
+ bool success = false;
+ if (pWHGIEPC && pWHGIEPC(&iecfg)) {
+ // We were read proxy config successfully.
+ success = true;
+ if (iecfg.fAutoDetect) {
+ proxy->autodetect = true;
+ }
+ if (iecfg.lpszAutoConfigUrl) {
+ proxy->autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl);
+ GlobalFree(iecfg.lpszAutoConfigUrl);
+ }
+ if (iecfg.lpszProxyBypass) {
+ proxy->bypass_list = ToUtf8(iecfg.lpszProxyBypass);
+ GlobalFree(iecfg.lpszProxyBypass);
+ }
+ if (iecfg.lpszProxy) {
+ if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+ ParseProxy(ToUtf8(iecfg.lpszProxy), proxy);
+ }
+ GlobalFree(iecfg.lpszProxy);
+ }
+ }
+ FreeLibrary(winhttp_handle);
+ return success;
+}
+
+// Uses the WinHTTP API to auto detect proxy for the given url. Firefox and IE
+// have slightly different option dialogs for proxy settings. In Firefox,
+// either a location of a proxy configuration file can be specified or auto
+// detection can be selected. In IE theese two options can be independently
+// selected. For the case where both options are selected (only IE) we try to
+// fetch the config file first, and if that fails we'll perform an auto
+// detection.
+//
+// Returns true if we successfully performed an auto detection not depending on
+// whether we found a proxy or not. Returns false on error.
+bool WinHttpAutoDetectProxyForUrl(const char* agent, const char* url,
+ ProxyInfo* proxy) {
+ Url<char> purl(url);
+ bool success = true;
+ HMODULE winhttp_handle = LoadLibrary(L"winhttp.dll");
+ if (winhttp_handle == NULL) {
+ LOG(LS_ERROR) << "Failed to load winhttp.dll.";
+ return false;
+ }
+ pfnWinHttpOpen pWHO =
+ reinterpret_cast<pfnWinHttpOpen>(GetProcAddress(winhttp_handle,
+ "WinHttpOpen"));
+ pfnWinHttpCloseHandle pWHCH =
+ reinterpret_cast<pfnWinHttpCloseHandle>(
+ GetProcAddress(winhttp_handle, "WinHttpCloseHandle"));
+ pfnWinHttpGetProxyForUrl pWHGPFU =
+ reinterpret_cast<pfnWinHttpGetProxyForUrl>(
+ GetProcAddress(winhttp_handle, "WinHttpGetProxyForUrl"));
+ if (pWHO && pWHCH && pWHGPFU) {
+ if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(),
+ WINHTTP_ACCESS_TYPE_NO_PROXY,
+ WINHTTP_NO_PROXY_NAME,
+ WINHTTP_NO_PROXY_BYPASS,
+ 0)) {
+ BOOL result = FALSE;
+ WINHTTP_PROXY_INFO info;
+ memset(&info, 0, sizeof(info));
+ if (proxy->autodetect) {
+ // Use DHCP and DNS to try to find any proxy to use.
+ WINHTTP_AUTOPROXY_OPTIONS options;
+ memset(&options, 0, sizeof(options));
+ options.fAutoLogonIfChallenged = TRUE;
+
+ options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT;
+ options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP
+ | WINHTTP_AUTO_DETECT_TYPE_DNS_A;
+ result = MyWinHttpGetProxyForUrl(
+ pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+ }
+ if (!result && !proxy->autoconfig_url.empty()) {
+ // We have the location of a proxy config file. Download it and
+ // execute it to find proxy settings for our url.
+ WINHTTP_AUTOPROXY_OPTIONS options;
+ memset(&options, 0, sizeof(options));
+ memset(&info, 0, sizeof(info));
+ options.fAutoLogonIfChallenged = TRUE;
+
+ std::wstring autoconfig_url16((ToUtf16)(proxy->autoconfig_url));
+ options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL;
+ options.lpszAutoConfigUrl = autoconfig_url16.c_str();
+
+ result = MyWinHttpGetProxyForUrl(
+ pWHGPFU, hWinHttp, ToUtf16(url).c_str(), &options, &info);
+ }
+ if (result) {
+ // Either the given auto config url was valid or auto
+ // detection found a proxy on this network.
+ if (info.lpszProxy) {
+ // TODO: Does this bypass list differ from the list
+ // retreived from GetWinHttpProxySettings earlier?
+ if (info.lpszProxyBypass) {
+ proxy->bypass_list = ToUtf8(info.lpszProxyBypass);
+ GlobalFree(info.lpszProxyBypass);
+ } else {
+ proxy->bypass_list.clear();
+ }
+ if (!ProxyListMatch(purl, proxy->bypass_list, ';')) {
+ // Found proxy for this URL. If parsing the address turns
+ // out ok then we are successful.
+ success = ParseProxy(ToUtf8(info.lpszProxy), proxy);
+ }
+ GlobalFree(info.lpszProxy);
+ }
+ } else {
+ // We could not find any proxy for this url.
+ LOG(LS_INFO) << "No proxy detected for " << url;
+ }
+ pWHCH(hWinHttp);
+ }
+ } else {
+ LOG(LS_ERROR) << "Failed loading WinHTTP functions.";
+ success = false;
+ }
+ FreeLibrary(winhttp_handle);
+ return success;
+}
+
+#if 0 // Below functions currently not used.
+
+bool GetJsProxySettings(const char* url, ProxyInfo* proxy) {
+ Url<char> purl(url);
+ bool success = false;
+
+ if (HMODULE hModJS = LoadLibrary(_T("jsproxy.dll"))) {
+ pfnInternetGetProxyInfo pIGPI =
+ reinterpret_cast<pfnInternetGetProxyInfo>(
+ GetProcAddress(hModJS, "InternetGetProxyInfo"));
+ if (pIGPI) {
+ char proxy[256], host[256];
+ memset(proxy, 0, sizeof(proxy));
+ char * ptr = proxy;
+ DWORD proxylen = sizeof(proxy);
+ std::string surl = Utf8String(url);
+ DWORD hostlen = _snprintf(host, sizeof(host), "http%s://%S",
+ purl.secure() ? "s" : "", purl.server());
+ if (pIGPI(surl.data(), surl.size(), host, hostlen, &ptr, &proxylen)) {
+ LOG(INFO) << "Proxy: " << proxy;
+ } else {
+ LOG_GLE(INFO) << "InternetGetProxyInfo";
+ }
+ }
+ FreeLibrary(hModJS);
+ }
+ return success;
+}
+
+bool GetWmProxySettings(const char* url, ProxyInfo* proxy) {
+ Url<char> purl(url);
+ bool success = false;
+
+ INSNetSourceCreator * nsc = 0;
+ HRESULT hr = CoCreateInstance(CLSID_ClientNetManager, 0, CLSCTX_ALL,
+ IID_INSNetSourceCreator, (LPVOID *) &nsc);
+ if (SUCCEEDED(hr)) {
+ if (SUCCEEDED(hr = nsc->Initialize())) {
+ VARIANT dispatch;
+ VariantInit(&dispatch);
+ if (SUCCEEDED(hr = nsc->GetNetSourceAdminInterface(L"http", &dispatch))) {
+ IWMSInternalAdminNetSource * ians = 0;
+ if (SUCCEEDED(hr = dispatch.pdispVal->QueryInterface(
+ IID_IWMSInternalAdminNetSource, (LPVOID *) &ians))) {
+ _bstr_t host(purl.server());
+ BSTR proxy = 0;
+ BOOL bProxyEnabled = FALSE;
+ DWORD port, context = 0;
+ if (SUCCEEDED(hr = ians->FindProxyForURL(
+ L"http", host, &bProxyEnabled, &proxy, &port, &context))) {
+ success = true;
+ if (bProxyEnabled) {
+ _bstr_t sproxy = proxy;
+ proxy->ptype = PT_HTTPS;
+ proxy->host = sproxy;
+ proxy->port = port;
+ }
+ }
+ SysFreeString(proxy);
+ if (FAILED(hr = ians->ShutdownProxyContext(context))) {
+ LOG(LS_INFO) << "IWMSInternalAdminNetSource::ShutdownProxyContext"
+ << "failed: " << hr;
+ }
+ ians->Release();
+ }
+ }
+ VariantClear(&dispatch);
+ if (FAILED(hr = nsc->Shutdown())) {
+ LOG(LS_INFO) << "INSNetSourceCreator::Shutdown failed: " << hr;
+ }
+ }
+ nsc->Release();
+ }
+ return success;
+}
+
+bool GetIePerConnectionProxySettings(const char* url, ProxyInfo* proxy) {
+ Url<char> purl(url);
+ bool success = false;
+
+ INTERNET_PER_CONN_OPTION_LIST list;
+ INTERNET_PER_CONN_OPTION options[3];
+ memset(&list, 0, sizeof(list));
+ memset(&options, 0, sizeof(options));
+
+ list.dwSize = sizeof(list);
+ list.dwOptionCount = 3;
+ list.pOptions = options;
+ options[0].dwOption = INTERNET_PER_CONN_FLAGS;
+ options[1].dwOption = INTERNET_PER_CONN_PROXY_SERVER;
+ options[2].dwOption = INTERNET_PER_CONN_PROXY_BYPASS;
+ DWORD dwSize = sizeof(list);
+
+ if (!InternetQueryOption(0, INTERNET_OPTION_PER_CONNECTION_OPTION, &list,
+ &dwSize)) {
+ LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+ } else if ((options[0].Value.dwValue & PROXY_TYPE_PROXY) != 0) {
+ success = true;
+ if (!ProxyListMatch(purl, nonnull(options[2].Value.pszValue), _T(';'))) {
+ ParseProxy(nonnull(options[1].Value.pszValue), proxy);
+ }
+ } else if ((options[0].Value.dwValue & PROXY_TYPE_DIRECT) != 0) {
+ success = true;
+ } else {
+ LOG(LS_INFO) << "unknown internet access type: "
+ << options[0].Value.dwValue;
+ }
+ if (options[1].Value.pszValue) {
+ GlobalFree(options[1].Value.pszValue);
+ }
+ if (options[2].Value.pszValue) {
+ GlobalFree(options[2].Value.pszValue);
+ }
+ return success;
+}
+
+#endif // 0
+
+// Uses the InternetQueryOption function to retrieve proxy settings
+// from the registry. This will only give us the 'static' settings,
+// ie, not any information about auto config etc.
+bool GetIeLanProxySettings(const char* url, ProxyInfo* proxy) {
+ Url<char> purl(url);
+ bool success = false;
+
+ wchar_t buffer[1024];
+ memset(buffer, 0, sizeof(buffer));
+ INTERNET_PROXY_INFO * info = reinterpret_cast<INTERNET_PROXY_INFO *>(buffer);
+ DWORD dwSize = sizeof(buffer);
+
+ if (!InternetQueryOption(0, INTERNET_OPTION_PROXY, info, &dwSize)) {
+ LOG(LS_INFO) << "InternetQueryOption failed: " << GetLastError();
+ } else if (info->dwAccessType == INTERNET_OPEN_TYPE_DIRECT) {
+ success = true;
+ } else if (info->dwAccessType == INTERNET_OPEN_TYPE_PROXY) {
+ success = true;
+ if (!ProxyListMatch(purl, nonnull(reinterpret_cast<const char*>(
+ info->lpszProxyBypass)), ' ')) {
+ ParseProxy(nonnull(reinterpret_cast<const char*>(info->lpszProxy)),
+ proxy);
+ }
+ } else {
+ LOG(LS_INFO) << "unknown internet access type: " << info->dwAccessType;
+ }
+ return success;
+}
+
+bool GetIeProxySettings(const char* agent, const char* url, ProxyInfo* proxy) {
+ bool success = GetWinHttpProxySettings(url, proxy);
+ if (!success) {
+ // TODO: Should always call this if no proxy were detected by
+ // GetWinHttpProxySettings?
+ // WinHttp failed. Try using the InternetOptionQuery method instead.
+ return GetIeLanProxySettings(url, proxy);
+ }
+ return true;
+}
+
+#endif // WIN32
+
+#ifdef OSX // OSX specific implementation for reading system wide
+ // proxy settings.
+
+bool p_getProxyInfoForTypeFromDictWithKeys(ProxyInfo* proxy,
+ ProxyType type,
+ const CFDictionaryRef proxyDict,
+ const CFStringRef enabledKey,
+ const CFStringRef hostKey,
+ const CFStringRef portKey) {
+ // whether or not we set up the proxy info.
+ bool result = false;
+
+ // we use this as a scratch variable for determining if operations
+ // succeeded.
+ bool converted = false;
+
+ // the data we need to construct the SocketAddress for the proxy.
+ std::string hostname;
+ int port;
+
+ if ((proxyDict != NULL) &&
+ (CFGetTypeID(proxyDict) == CFDictionaryGetTypeID())) {
+ // CoreFoundation stuff that we'll have to get from
+ // the dictionaries and interpret or convert into more usable formats.
+ CFNumberRef enabledCFNum;
+ CFNumberRef portCFNum;
+ CFStringRef hostCFStr;
+
+ enabledCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, enabledKey);
+
+ if (p_isCFNumberTrue(enabledCFNum)) {
+ // let's see if we can get the address and port.
+ hostCFStr = (CFStringRef)CFDictionaryGetValue(proxyDict, hostKey);
+ converted = p_convertHostCFStringRefToCPPString(hostCFStr, hostname);
+ if (converted) {
+ portCFNum = (CFNumberRef)CFDictionaryGetValue(proxyDict, portKey);
+ converted = p_convertCFNumberToInt(portCFNum, &port);
+ if (converted) {
+ // we have something enabled, with a hostname and a port.
+ // That's sufficient to set up the proxy info.
+ proxy->type = type;
+ proxy->address.SetIP(hostname);
+ proxy->address.SetPort(port);
+ result = true;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+// Looks for proxy information in the given dictionary,
+// return true if it found sufficient information to define one,
+// false otherwise. This is guaranteed to not change the values in proxy
+// unless a full-fledged proxy description was discovered in the dictionary.
+// However, at the present time this does not support username or password.
+// Checks first for a SOCKS proxy, then for HTTPS, then HTTP.
+bool GetMacProxySettingsFromDictionary(ProxyInfo* proxy,
+ const CFDictionaryRef proxyDict) {
+ // the function result.
+ bool gotProxy = false;
+
+
+ // first we see if there's a SOCKS proxy in place.
+ gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+ PROXY_SOCKS5,
+ proxyDict,
+ kSCPropNetProxiesSOCKSEnable,
+ kSCPropNetProxiesSOCKSProxy,
+ kSCPropNetProxiesSOCKSPort);
+
+ if (!gotProxy) {
+ // okay, no SOCKS proxy, let's look for https.
+ gotProxy = p_getProxyInfoForTypeFromDictWithKeys(proxy,
+ PROXY_HTTPS,
+ proxyDict,
+ kSCPropNetProxiesHTTPSEnable,
+ kSCPropNetProxiesHTTPSProxy,
+ kSCPropNetProxiesHTTPSPort);
+ if (!gotProxy) {
+ // Finally, try HTTP proxy. Note that flute doesn't
+ // differentiate between HTTPS and HTTP, hence we are using the
+ // same flute type here, ie. PROXY_HTTPS.
+ gotProxy = p_getProxyInfoForTypeFromDictWithKeys(
+ proxy, PROXY_HTTPS, proxyDict, kSCPropNetProxiesHTTPEnable,
+ kSCPropNetProxiesHTTPProxy, kSCPropNetProxiesHTTPPort);
+ }
+ }
+ return gotProxy;
+}
+
+bool p_putPasswordInProxyInfo(ProxyInfo* proxy) {
+ bool result = true; // by default we assume we're good.
+ // for all we know there isn't any password. We'll set to false
+ // if we find a problem.
+
+ // Ask the keychain for an internet password search for the given protocol.
+ OSStatus oss = 0;
+ SecKeychainAttributeList attrList;
+ attrList.count = 3;
+ SecKeychainAttribute attributes[3];
+ attrList.attr = attributes;
+
+ attributes[0].tag = kSecProtocolItemAttr;
+ attributes[0].length = sizeof(SecProtocolType);
+ SecProtocolType protocol;
+ switch (proxy->type) {
+ case PROXY_HTTPS :
+ protocol = kSecProtocolTypeHTTPS;
+ break;
+ case PROXY_SOCKS5 :
+ protocol = kSecProtocolTypeSOCKS;
+ break;
+ default :
+ LOG(LS_ERROR) << "asked for proxy password for unknown proxy type.";
+ result = false;
+ break;
+ }
+ attributes[0].data = &protocol;
+
+ UInt32 port = proxy->address.port();
+ attributes[1].tag = kSecPortItemAttr;
+ attributes[1].length = sizeof(UInt32);
+ attributes[1].data = &port;
+
+ std::string ip = proxy->address.IPAsString();
+ attributes[2].tag = kSecServerItemAttr;
+ attributes[2].length = ip.length();
+ attributes[2].data = const_cast<char*>(ip.c_str());
+
+ if (result) {
+ LOG(LS_INFO) << "trying to get proxy username/password";
+ SecKeychainSearchRef sref;
+ oss = SecKeychainSearchCreateFromAttributes(NULL,
+ kSecInternetPasswordItemClass,
+ &attrList, &sref);
+ if (0 == oss) {
+ LOG(LS_INFO) << "SecKeychainSearchCreateFromAttributes was good";
+ // Get the first item, if there is one.
+ SecKeychainItemRef iref;
+ oss = SecKeychainSearchCopyNext(sref, &iref);
+ if (0 == oss) {
+ LOG(LS_INFO) << "...looks like we have the username/password data";
+ // If there is, get the username and the password.
+
+ SecKeychainAttributeInfo attribsToGet;
+ attribsToGet.count = 1;
+ UInt32 tag = kSecAccountItemAttr;
+ UInt32 format = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+ void *data;
+ UInt32 length;
+ SecKeychainAttributeList *localList;
+
+ attribsToGet.tag = &tag;
+ attribsToGet.format = &format;
+ OSStatus copyres = SecKeychainItemCopyAttributesAndData(iref,
+ &attribsToGet,
+ NULL,
+ &localList,
+ &length,
+ &data);
+ if (0 == copyres) {
+ LOG(LS_INFO) << "...and we can pull it out.";
+ // now, we know from experimentation (sadly not from docs)
+ // that the username is in the local attribute list,
+ // and the password in the data,
+ // both without null termination but with info on their length.
+ // grab the password from the data.
+ std::string password;
+ password.append(static_cast<const char*>(data), length);
+
+ // make the password into a CryptString
+ // huh, at the time of writing, you can't.
+ // so we'll skip that for now and come back to it later.
+
+ // now put the username in the proxy.
+ if (1 <= localList->attr->length) {
+ proxy->username.append(
+ static_cast<const char*>(localList->attr->data),
+ localList->attr->length);
+ LOG(LS_INFO) << "username is " << proxy->username;
+ } else {
+ LOG(LS_ERROR) << "got keychain entry with no username";
+ result = false;
+ }
+ } else {
+ LOG(LS_ERROR) << "couldn't copy info from keychain.";
+ result = false;
+ }
+ SecKeychainItemFreeAttributesAndData(localList, data);
+ } else if (errSecItemNotFound == oss) {
+ LOG(LS_INFO) << "...username/password info not found";
+ } else {
+ // oooh, neither 0 nor itemNotFound.
+ LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+ result = false;
+ }
+ } else if (errSecItemNotFound == oss) { // noop
+ } else {
+ // oooh, neither 0 nor itemNotFound.
+ LOG(LS_ERROR) << "Couldn't get keychain information, error code" << oss;
+ result = false;
+ }
+ }
+
+ return result;
+}
+
+bool GetMacProxySettings(ProxyInfo* proxy) {
+ // based on the Apple Technical Q&A QA1234
+ // http://developer.apple.com/qa/qa2001/qa1234.html
+ CFDictionaryRef proxyDict = SCDynamicStoreCopyProxies(NULL);
+ bool result = false;
+
+ if (proxyDict != NULL) {
+ // sending it off to another function makes it easier to unit test
+ // since we can make our own dictionary to hand to that function.
+ result = GetMacProxySettingsFromDictionary(proxy, proxyDict);
+
+ if (result) {
+ result = p_putPasswordInProxyInfo(proxy);
+ }
+
+ // We created the dictionary with something that had the
+ // word 'copy' in it, so we have to release it, according
+ // to the Carbon memory management standards.
+ CFRelease(proxyDict);
+ } else {
+ LOG(LS_ERROR) << "SCDynamicStoreCopyProxies failed";
+ }
+
+ return result;
+}
+#endif // OSX
+
+bool AutoDetectProxySettings(const char* agent, const char* url,
+ ProxyInfo* proxy) {
+#ifdef WIN32
+ return WinHttpAutoDetectProxyForUrl(agent, url, proxy);
+#else
+ LOG(LS_WARNING) << "Proxy auto-detection not implemented for this platform";
+ return false;
+#endif
+}
+
+bool GetSystemDefaultProxySettings(const char* agent, const char* url,
+ ProxyInfo* proxy) {
+#ifdef WIN32
+ return GetIeProxySettings(agent, url, proxy);
+#elif OSX
+ return GetMacProxySettings(proxy);
+#else
+ // TODO: Get System settings if browser is not firefox.
+ return GetFirefoxProxySettings(url, proxy);
+#endif
+}
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+ ProxyInfo& proxy, bool long_operation) {
+ UserAgent a = GetAgent(agent);
+ bool result;
+ switch (a) {
+ case UA_FIREFOX: {
+ result = GetFirefoxProxySettings(url, &proxy);
+ break;
+ }
+#ifdef WIN32
+ case UA_INTERNETEXPLORER:
+ result = GetIeProxySettings(agent, url, &proxy);
+ break;
+ case UA_UNKNOWN:
+ // Agent not defined, check default browser.
+ if (IsDefaultBrowserFirefox()) {
+ result = GetFirefoxProxySettings(url, &proxy);
+ } else {
+ result = GetIeProxySettings(agent, url, &proxy);
+ }
+ break;
+#endif // WIN32
+ default:
+ result = GetSystemDefaultProxySettings(agent, url, &proxy);
+ break;
+ }
+
+ // TODO: Consider using the 'long_operation' parameter to
+ // decide whether to do the auto detection.
+ if (result && (proxy.autodetect ||
+ !proxy.autoconfig_url.empty())) {
+ // Use WinHTTP to auto detect proxy for us.
+ result = AutoDetectProxySettings(agent, url, &proxy);
+ if (!result) {
+ // Either auto detection is not supported or we simply didn't
+ // find any proxy, reset type.
+ proxy.type = talk_base::PROXY_NONE;
+ }
+ }
+ return result;
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxydetect.h b/talk/base/proxydetect.h
new file mode 100644
index 0000000..36ee672
--- /dev/null
+++ b/talk/base/proxydetect.h
@@ -0,0 +1,48 @@
+/*
+ * libjingle
+ * Copyright 2007, 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 _PROXYDETECT_H_
+#define _PROXYDETECT_H_
+
+#include "talk/base/proxyinfo.h"
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+namespace talk_base {
+// Auto-detect the proxy server. Returns true if a proxy is configured,
+// although hostname may be empty if the proxy is not required for
+// the given URL.
+
+bool GetProxySettingsForUrl(const char* agent, const char* url,
+ talk_base::ProxyInfo& proxy,
+ bool long_operation = false);
+
+} // namespace talk_base
+
+#endif // _PROXYDETECT_H_
diff --git a/talk/base/proxyinfo.cc b/talk/base/proxyinfo.cc
new file mode 100644
index 0000000..1d9c588
--- /dev/null
+++ b/talk/base/proxyinfo.cc
@@ -0,0 +1,37 @@
+/*
+ * 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 "talk/base/proxyinfo.h"
+
+namespace talk_base {
+
+const char * ProxyToString(ProxyType proxy) {
+ const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" };
+ return PROXY_NAMES[proxy];
+}
+
+} // namespace talk_base
diff --git a/talk/base/proxyinfo.h b/talk/base/proxyinfo.h
new file mode 100644
index 0000000..9e28f1a
--- /dev/null
+++ b/talk/base/proxyinfo.h
@@ -0,0 +1,59 @@
+/*
+ * 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_PROXYINFO_H__
+#define TALK_BASE_PROXYINFO_H__
+
+#include <string>
+#include "talk/base/socketaddress.h"
+#include "talk/base/cryptstring.h"
+
+namespace talk_base {
+
+enum ProxyType {
+ PROXY_NONE,
+ PROXY_HTTPS,
+ PROXY_SOCKS5,
+ PROXY_UNKNOWN
+};
+const char * ProxyToString(ProxyType proxy);
+
+struct ProxyInfo {
+ ProxyType type;
+ SocketAddress address;
+ std::string autoconfig_url;
+ bool autodetect;
+ std::string bypass_list;
+ std::string username;
+ CryptString password;
+
+ ProxyInfo() : type(PROXY_NONE), autodetect(false) { }
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_PROXYINFO_H__
diff --git a/talk/base/ratetracker.cc b/talk/base/ratetracker.cc
new file mode 100644
index 0000000..d5207cd
--- /dev/null
+++ b/talk/base/ratetracker.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/ratetracker.h"
+#include "talk/base/time.h"
+
+namespace talk_base {
+
+RateTracker::RateTracker()
+ : total_units_(0), units_second_(0),
+ last_units_second_time_(static_cast<uint32>(-1)),
+ last_units_second_calc_(0) {
+}
+
+size_t RateTracker::total_units() const {
+ return total_units_;
+}
+
+size_t RateTracker::units_second() {
+ // Snapshot units / second calculator. Determine how many seconds have
+ // elapsed since our last reference point. If over 1 second, establish
+ // a new reference point that is an integer number of seconds since the
+ // last one, and compute the units over that interval.
+ uint32 current_time = Time();
+ if (last_units_second_time_ != static_cast<uint32>(-1)) {
+ int delta = talk_base::TimeDiff(current_time, last_units_second_time_);
+ if (delta >= 1000) {
+ int fraction_time = delta % 1000;
+ int seconds = delta / 1000;
+ int fraction_units =
+ static_cast<int>(total_units_ - last_units_second_calc_) *
+ fraction_time / delta;
+ // Compute "units received during the interval" / "seconds in interval"
+ units_second_ =
+ (total_units_ - last_units_second_calc_ - fraction_units) / seconds;
+ last_units_second_time_ = current_time - fraction_time;
+ last_units_second_calc_ = total_units_ - fraction_units;
+ }
+ }
+ if (last_units_second_time_ == static_cast<uint32>(-1)) {
+ last_units_second_time_ = current_time;
+ last_units_second_calc_ = total_units_;
+ }
+
+ return units_second_;
+}
+
+void RateTracker::Update(size_t units) {
+ total_units_ += units;
+}
+
+uint32 RateTracker::Time() const {
+ return talk_base::Time();
+}
+
+} // namespace talk_base
diff --git a/talk/base/ratetracker.h b/talk/base/ratetracker.h
new file mode 100644
index 0000000..28c7bb3
--- /dev/null
+++ b/talk/base/ratetracker.h
@@ -0,0 +1,59 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_RATETRACKER_H_
+#define TALK_BASE_RATETRACKER_H_
+
+#include <stdlib.h>
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// Computes instantaneous units per second.
+class RateTracker {
+ public:
+ RateTracker();
+ virtual ~RateTracker() {}
+
+ size_t total_units() const;
+ size_t units_second();
+ void Update(size_t units);
+
+ protected:
+ // overrideable for tests
+ virtual uint32 Time() const;
+
+ private:
+ size_t total_units_;
+ size_t units_second_;
+ uint32 last_units_second_time_;
+ size_t last_units_second_calc_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_RATETRACKER_H_
diff --git a/talk/base/schanneladapter.cc b/talk/base/schanneladapter.cc
new file mode 100644
index 0000000..01aa9ce
--- /dev/null
+++ b/talk/base/schanneladapter.cc
@@ -0,0 +1,722 @@
+/*
+ * 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 "talk/base/win32.h"
+#define SECURITY_WIN32
+#include <security.h>
+#include <schannel.h>
+
+#include <iomanip>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/schanneladapter.h"
+#include "talk/base/sec_buffer.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// SChannelAdapter
+/////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SCHANNEL_BUFFER_TYPES[] = {
+ KLABEL(SECBUFFER_EMPTY), // 0
+ KLABEL(SECBUFFER_DATA), // 1
+ KLABEL(SECBUFFER_TOKEN), // 2
+ KLABEL(SECBUFFER_PKG_PARAMS), // 3
+ KLABEL(SECBUFFER_MISSING), // 4
+ KLABEL(SECBUFFER_EXTRA), // 5
+ KLABEL(SECBUFFER_STREAM_TRAILER), // 6
+ KLABEL(SECBUFFER_STREAM_HEADER), // 7
+ KLABEL(SECBUFFER_MECHLIST), // 11
+ KLABEL(SECBUFFER_MECHLIST_SIGNATURE), // 12
+ KLABEL(SECBUFFER_TARGET), // 13
+ KLABEL(SECBUFFER_CHANNEL_BINDINGS), // 14
+ LASTLABEL
+};
+
+void DescribeBuffer(LoggingSeverity severity, const char* prefix,
+ const SecBuffer& sb) {
+ LOG_V(severity)
+ << prefix
+ << "(" << sb.cbBuffer
+ << ", " << FindLabel(sb.BufferType & ~SECBUFFER_ATTRMASK,
+ SCHANNEL_BUFFER_TYPES)
+ << ", " << sb.pvBuffer << ")";
+}
+
+void DescribeBuffers(LoggingSeverity severity, const char* prefix,
+ const SecBufferDesc* sbd) {
+ if (!LOG_CHECK_LEVEL_V(severity))
+ return;
+ LOG_V(severity) << prefix << "(";
+ for (size_t i=0; i<sbd->cBuffers; ++i) {
+ DescribeBuffer(severity, " ", sbd->pBuffers[i]);
+ }
+ LOG_V(severity) << ")";
+}
+
+const ULONG SSL_FLAGS_DEFAULT = ISC_REQ_ALLOCATE_MEMORY
+ | ISC_REQ_CONFIDENTIALITY
+ | ISC_REQ_EXTENDED_ERROR
+ | ISC_REQ_INTEGRITY
+ | ISC_REQ_REPLAY_DETECT
+ | ISC_REQ_SEQUENCE_DETECT
+ | ISC_REQ_STREAM;
+ //| ISC_REQ_USE_SUPPLIED_CREDS;
+
+typedef std::vector<char> SChannelBuffer;
+
+struct SChannelAdapter::SSLImpl {
+ CredHandle cred;
+ CtxtHandle ctx;
+ bool cred_init, ctx_init;
+ SChannelBuffer inbuf, outbuf, readable;
+ SecPkgContext_StreamSizes sizes;
+
+ SSLImpl() : cred_init(false), ctx_init(false) { }
+};
+
+SChannelAdapter::SChannelAdapter(AsyncSocket* socket)
+ : SSLAdapter(socket), state_(SSL_NONE),
+ restartable_(false), signal_close_(false), message_pending_(false),
+ impl_(new SSLImpl) {
+}
+
+SChannelAdapter::~SChannelAdapter() {
+ Cleanup();
+}
+
+int
+SChannelAdapter::StartSSL(const char* hostname, bool restartable) {
+ if (state_ != SSL_NONE)
+ return ERROR_ALREADY_INITIALIZED;
+
+ ssl_host_name_ = hostname;
+ restartable_ = restartable;
+
+ if (socket_->GetState() != Socket::CS_CONNECTED) {
+ state_ = SSL_WAIT;
+ return 0;
+ }
+
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ Error("BeginSSL", err, false);
+ return err;
+ }
+
+ return 0;
+}
+
+int
+SChannelAdapter::BeginSSL() {
+ LOG(LS_VERBOSE) << "BeginSSL: " << ssl_host_name_;
+ ASSERT(state_ == SSL_CONNECTING);
+
+ SECURITY_STATUS ret;
+
+ SCHANNEL_CRED sc_cred = { 0 };
+ sc_cred.dwVersion = SCHANNEL_CRED_VERSION;
+ //sc_cred.dwMinimumCipherStrength = 128; // Note: use system default
+ sc_cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS | SCH_CRED_AUTO_CRED_VALIDATION;
+
+ ret = AcquireCredentialsHandle(NULL, UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL,
+ &sc_cred, NULL, NULL, &impl_->cred, NULL);
+ if (ret != SEC_E_OK) {
+ LOG(LS_ERROR) << "AcquireCredentialsHandle error: "
+ << ErrorName(ret, SECURITY_ERRORS);
+ return ret;
+ }
+ impl_->cred_init = true;
+
+ if (LOG_CHECK_LEVEL(LS_VERBOSE)) {
+ SecPkgCred_CipherStrengths cipher_strengths = { 0 };
+ ret = QueryCredentialsAttributes(&impl_->cred,
+ SECPKG_ATTR_CIPHER_STRENGTHS,
+ &cipher_strengths);
+ if (SUCCEEDED(ret)) {
+ LOG(LS_VERBOSE) << "SChannel cipher strength: "
+ << cipher_strengths.dwMinimumCipherStrength << " - "
+ << cipher_strengths.dwMaximumCipherStrength;
+ }
+
+ SecPkgCred_SupportedAlgs supported_algs = { 0 };
+ ret = QueryCredentialsAttributes(&impl_->cred,
+ SECPKG_ATTR_SUPPORTED_ALGS,
+ &supported_algs);
+ if (SUCCEEDED(ret)) {
+ LOG(LS_VERBOSE) << "SChannel supported algorithms:";
+ for (DWORD i=0; i<supported_algs.cSupportedAlgs; ++i) {
+ ALG_ID alg_id = supported_algs.palgSupportedAlgs[i];
+ PCCRYPT_OID_INFO oinfo = CryptFindOIDInfo(CRYPT_OID_INFO_ALGID_KEY,
+ &alg_id, 0);
+ LPCWSTR alg_name = (NULL != oinfo) ? oinfo->pwszName : L"Unknown";
+ LOG(LS_VERBOSE) << " " << ToUtf8(alg_name) << " (" << alg_id << ")";
+ }
+ CSecBufferBase::FreeSSPI(supported_algs.palgSupportedAlgs);
+ }
+ }
+
+ ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+ if (ignore_bad_cert())
+ flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+ CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+ ret = InitializeSecurityContextA(&impl_->cred, NULL,
+ const_cast<char*>(ssl_host_name_.c_str()),
+ flags, 0, 0, NULL, 0,
+ &impl_->ctx, sb_out.desc(),
+ &ret_flags, NULL);
+ if (SUCCEEDED(ret))
+ impl_->ctx_init = true;
+ return ProcessContext(ret, NULL, sb_out.desc());
+}
+
+int
+SChannelAdapter::ContinueSSL() {
+ LOG(LS_VERBOSE) << "ContinueSSL";
+ ASSERT(state_ == SSL_CONNECTING);
+
+ SECURITY_STATUS ret;
+
+ CSecBufferBundle<2> sb_in;
+ sb_in[0].BufferType = SECBUFFER_TOKEN;
+ sb_in[0].cbBuffer = static_cast<unsigned long>(impl_->inbuf.size());
+ sb_in[0].pvBuffer = &impl_->inbuf[0];
+ //DescribeBuffers(LS_VERBOSE, "Input Buffer ", sb_in.desc());
+
+ ULONG flags = SSL_FLAGS_DEFAULT, ret_flags = 0;
+ if (ignore_bad_cert())
+ flags |= ISC_REQ_MANUAL_CRED_VALIDATION;
+
+ CSecBufferBundle<2, CSecBufferBase::FreeSSPI> sb_out;
+ ret = InitializeSecurityContextA(&impl_->cred, &impl_->ctx,
+ const_cast<char*>(ssl_host_name_.c_str()),
+ flags, 0, 0, sb_in.desc(), 0,
+ NULL, sb_out.desc(),
+ &ret_flags, NULL);
+ return ProcessContext(ret, sb_in.desc(), sb_out.desc());
+}
+
+int
+SChannelAdapter::ProcessContext(long int status, _SecBufferDesc* sbd_in,
+ _SecBufferDesc* sbd_out) {
+ LoggingSeverity level = LS_ERROR;
+ if ((status == SEC_E_OK)
+ || (status != SEC_I_CONTINUE_NEEDED)
+ || (status != SEC_E_INCOMPLETE_MESSAGE)) {
+ level = LS_VERBOSE; // Expected messages
+ }
+ LOG_V(level)
+ << "InitializeSecurityContext error: "
+ << ErrorName(status, SECURITY_ERRORS);
+ //if (sbd_in)
+ // DescribeBuffers(LS_VERBOSE, "Input Buffer ", sbd_in);
+ //if (sbd_out)
+ // DescribeBuffers(LS_VERBOSE, "Output Buffer ", sbd_out);
+
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ // Wait for more input from server.
+ return Flush();
+ }
+
+ if (FAILED(status)) {
+ // We can't continue. Common errors:
+ // SEC_E_CERT_EXPIRED - Typically, this means the computer clock is wrong.
+ return status;
+ }
+
+ // Note: we check both input and output buffers for SECBUFFER_EXTRA.
+ // Experience shows it appearing in the input, but the documentation claims
+ // it should appear in the output.
+ size_t extra = 0;
+ if (sbd_in) {
+ for (size_t i=0; i<sbd_in->cBuffers; ++i) {
+ SecBuffer& buffer = sbd_in->pBuffers[i];
+ if (buffer.BufferType == SECBUFFER_EXTRA) {
+ extra += buffer.cbBuffer;
+ }
+ }
+ }
+ if (sbd_out) {
+ for (size_t i=0; i<sbd_out->cBuffers; ++i) {
+ SecBuffer& buffer = sbd_out->pBuffers[i];
+ if (buffer.BufferType == SECBUFFER_EXTRA) {
+ extra += buffer.cbBuffer;
+ } else if (buffer.BufferType == SECBUFFER_TOKEN) {
+ impl_->outbuf.insert(impl_->outbuf.end(),
+ reinterpret_cast<char*>(buffer.pvBuffer),
+ reinterpret_cast<char*>(buffer.pvBuffer) + buffer.cbBuffer);
+ }
+ }
+ }
+
+ if (extra) {
+ ASSERT(extra <= impl_->inbuf.size());
+ size_t consumed = impl_->inbuf.size() - extra;
+ memmove(&impl_->inbuf[0], &impl_->inbuf[consumed], extra);
+ impl_->inbuf.resize(extra);
+ } else {
+ impl_->inbuf.clear();
+ }
+
+ if (SEC_I_CONTINUE_NEEDED == status) {
+ // Send data to server and wait for response.
+ // Note: ContinueSSL will result in a Flush, anyway.
+ return impl_->inbuf.empty() ? Flush() : ContinueSSL();
+ }
+
+ if (SEC_E_OK == status) {
+ LOG(LS_VERBOSE) << "QueryContextAttributes";
+ status = QueryContextAttributes(&impl_->ctx, SECPKG_ATTR_STREAM_SIZES,
+ &impl_->sizes);
+ if (FAILED(status)) {
+ LOG(LS_ERROR) << "QueryContextAttributes error: "
+ << ErrorName(status, SECURITY_ERRORS);
+ return status;
+ }
+
+ state_ = SSL_CONNECTED;
+
+ if (int err = DecryptData()) {
+ return err;
+ } else if (int err = Flush()) {
+ return err;
+ } else {
+ // If we decrypted any data, queue up a notification here
+ PostEvent();
+ // Signal our connectedness
+ AsyncSocketAdapter::OnConnectEvent(this);
+ }
+ return 0;
+ }
+
+ if (SEC_I_INCOMPLETE_CREDENTIALS == status) {
+ // We don't support client authentication in schannel.
+ return status;
+ }
+
+ // We don't expect any other codes
+ ASSERT(false);
+ return status;
+}
+
+int
+SChannelAdapter::DecryptData() {
+ SChannelBuffer& inbuf = impl_->inbuf;
+ SChannelBuffer& readable = impl_->readable;
+
+ while (!inbuf.empty()) {
+ CSecBufferBundle<4> in_buf;
+ in_buf[0].BufferType = SECBUFFER_DATA;
+ in_buf[0].cbBuffer = static_cast<unsigned long>(inbuf.size());
+ in_buf[0].pvBuffer = &inbuf[0];
+
+ //DescribeBuffers(LS_VERBOSE, "Decrypt In ", in_buf.desc());
+ SECURITY_STATUS status = DecryptMessage(&impl_->ctx, in_buf.desc(), 0, 0);
+ //DescribeBuffers(LS_VERBOSE, "Decrypt Out ", in_buf.desc());
+
+ // Note: We are explicitly treating SEC_E_OK, SEC_I_CONTEXT_EXPIRED, and
+ // any other successful results as continue.
+ if (SUCCEEDED(status)) {
+ size_t data_len = 0, extra_len = 0;
+ for (size_t i=0; i<in_buf.desc()->cBuffers; ++i) {
+ if (in_buf[i].BufferType == SECBUFFER_DATA) {
+ data_len += in_buf[i].cbBuffer;
+ readable.insert(readable.end(),
+ reinterpret_cast<char*>(in_buf[i].pvBuffer),
+ reinterpret_cast<char*>(in_buf[i].pvBuffer) + in_buf[i].cbBuffer);
+ } else if (in_buf[i].BufferType == SECBUFFER_EXTRA) {
+ extra_len += in_buf[i].cbBuffer;
+ }
+ }
+ // There is a bug on Win2K where SEC_I_CONTEXT_EXPIRED is misclassified.
+ if ((data_len == 0) && (inbuf[0] == 0x15)) {
+ status = SEC_I_CONTEXT_EXPIRED;
+ }
+ if (extra_len) {
+ size_t consumed = inbuf.size() - extra_len;
+ memmove(&inbuf[0], &inbuf[consumed], extra_len);
+ inbuf.resize(extra_len);
+ } else {
+ inbuf.clear();
+ }
+ // TODO: Handle SEC_I_CONTEXT_EXPIRED to do clean shutdown
+ if (status != SEC_E_OK) {
+ LOG(LS_INFO) << "DecryptMessage returned continuation code: "
+ << ErrorName(status, SECURITY_ERRORS);
+ }
+ continue;
+ }
+
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ break;
+ } else {
+ return status;
+ }
+ }
+
+ return 0;
+}
+
+void
+SChannelAdapter::Cleanup() {
+ if (impl_->ctx_init)
+ DeleteSecurityContext(&impl_->ctx);
+ if (impl_->cred_init)
+ FreeCredentialsHandle(&impl_->cred);
+ delete impl_;
+}
+
+void
+SChannelAdapter::PostEvent() {
+ // Check if there's anything notable to signal
+ if (impl_->readable.empty() && !signal_close_)
+ return;
+
+ // Only one post in the queue at a time
+ if (message_pending_)
+ return;
+
+ if (Thread* thread = Thread::Current()) {
+ message_pending_ = true;
+ thread->Post(this);
+ } else {
+ LOG(LS_ERROR) << "No thread context available for SChannelAdapter";
+ ASSERT(false);
+ }
+}
+
+void
+SChannelAdapter::Error(const char* context, int err, bool signal) {
+ LOG(LS_WARNING) << "SChannelAdapter::Error("
+ << context << ", "
+ << ErrorName(err, SECURITY_ERRORS) << ")";
+ state_ = SSL_ERROR;
+ SetError(err);
+ if (signal)
+ AsyncSocketAdapter::OnCloseEvent(this, err);
+}
+
+int
+SChannelAdapter::Read() {
+ char buffer[4096];
+ SChannelBuffer& inbuf = impl_->inbuf;
+ while (true) {
+ int ret = AsyncSocketAdapter::Recv(buffer, sizeof(buffer));
+ if (ret > 0) {
+ inbuf.insert(inbuf.end(), buffer, buffer + ret);
+ } else if (GetError() == EWOULDBLOCK) {
+ return 0; // Blocking
+ } else {
+ return GetError();
+ }
+ }
+}
+
+int
+SChannelAdapter::Flush() {
+ int result = 0;
+ size_t pos = 0;
+ SChannelBuffer& outbuf = impl_->outbuf;
+ while (pos < outbuf.size()) {
+ int sent = AsyncSocketAdapter::Send(&outbuf[pos], outbuf.size() - pos);
+ if (sent > 0) {
+ pos += sent;
+ } else if (GetError() == EWOULDBLOCK) {
+ break; // Blocking
+ } else {
+ result = GetError();
+ break;
+ }
+ }
+ if (int remainder = outbuf.size() - pos) {
+ memmove(&outbuf[0], &outbuf[pos], remainder);
+ outbuf.resize(remainder);
+ } else {
+ outbuf.clear();
+ }
+ return result;
+}
+
+//
+// AsyncSocket Implementation
+//
+
+int
+SChannelAdapter::Send(const void* pv, size_t cb) {
+ switch (state_) {
+ case SSL_NONE:
+ return AsyncSocketAdapter::Send(pv, cb);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ SetError(EWOULDBLOCK);
+ return SOCKET_ERROR;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_ERROR:
+ default:
+ return SOCKET_ERROR;
+ }
+
+ size_t written = 0;
+ SChannelBuffer& outbuf = impl_->outbuf;
+ while (written < cb) {
+ const size_t encrypt_len = std::min<size_t>(cb - written,
+ impl_->sizes.cbMaximumMessage);
+
+ CSecBufferBundle<4> out_buf;
+ out_buf[0].BufferType = SECBUFFER_STREAM_HEADER;
+ out_buf[0].cbBuffer = impl_->sizes.cbHeader;
+ out_buf[1].BufferType = SECBUFFER_DATA;
+ out_buf[1].cbBuffer = static_cast<unsigned long>(encrypt_len);
+ out_buf[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ out_buf[2].cbBuffer = impl_->sizes.cbTrailer;
+
+ size_t packet_len = out_buf[0].cbBuffer
+ + out_buf[1].cbBuffer
+ + out_buf[2].cbBuffer;
+
+ SChannelBuffer message;
+ message.resize(packet_len);
+ out_buf[0].pvBuffer = &message[0];
+ out_buf[1].pvBuffer = &message[out_buf[0].cbBuffer];
+ out_buf[2].pvBuffer = &message[out_buf[0].cbBuffer + out_buf[1].cbBuffer];
+
+ memcpy(out_buf[1].pvBuffer,
+ static_cast<const char*>(pv) + written,
+ encrypt_len);
+
+ //DescribeBuffers(LS_VERBOSE, "Encrypt In ", out_buf.desc());
+ SECURITY_STATUS res = EncryptMessage(&impl_->ctx, 0, out_buf.desc(), 0);
+ //DescribeBuffers(LS_VERBOSE, "Encrypt Out ", out_buf.desc());
+
+ if (FAILED(res)) {
+ Error("EncryptMessage", res, false);
+ return SOCKET_ERROR;
+ }
+
+ // We assume that the header and data segments do not change length,
+ // or else encrypting the concatenated packet in-place is wrong.
+ ASSERT(out_buf[0].cbBuffer == impl_->sizes.cbHeader);
+ ASSERT(out_buf[1].cbBuffer == static_cast<unsigned long>(encrypt_len));
+
+ // However, the length of the trailer may change due to padding.
+ ASSERT(out_buf[2].cbBuffer <= impl_->sizes.cbTrailer);
+
+ packet_len = out_buf[0].cbBuffer
+ + out_buf[1].cbBuffer
+ + out_buf[2].cbBuffer;
+
+ written += encrypt_len;
+ outbuf.insert(outbuf.end(), &message[0], &message[packet_len-1]+1);
+ }
+
+ if (int err = Flush()) {
+ state_ = SSL_ERROR;
+ SetError(err);
+ return SOCKET_ERROR;
+ }
+
+ return static_cast<int>(written);
+}
+
+int
+SChannelAdapter::Recv(void* pv, size_t cb) {
+ switch (state_) {
+ case SSL_NONE:
+ return AsyncSocketAdapter::Recv(pv, cb);
+
+ case SSL_WAIT:
+ case SSL_CONNECTING:
+ SetError(EWOULDBLOCK);
+ return SOCKET_ERROR;
+
+ case SSL_CONNECTED:
+ break;
+
+ case SSL_ERROR:
+ default:
+ return SOCKET_ERROR;
+ }
+
+ SChannelBuffer& readable = impl_->readable;
+ if (readable.empty()) {
+ SetError(EWOULDBLOCK);
+ return SOCKET_ERROR;
+ }
+ size_t read = _min(cb, readable.size());
+ memcpy(pv, &readable[0], read);
+ if (size_t remaining = readable.size() - read) {
+ memmove(&readable[0], &readable[read], remaining);
+ readable.resize(remaining);
+ } else {
+ readable.clear();
+ }
+
+ PostEvent();
+ return static_cast<int>(read);
+}
+
+int
+SChannelAdapter::Close() {
+ if (!impl_->readable.empty()) {
+ LOG(WARNING) << "SChannelAdapter::Close with readable data";
+ // Note: this isn't strictly an error, but we're using it temporarily to
+ // track bugs.
+ //ASSERT(false);
+ }
+ if (state_ == SSL_CONNECTED) {
+ DWORD token = SCHANNEL_SHUTDOWN;
+ CSecBufferBundle<1> sb_in;
+ sb_in[0].BufferType = SECBUFFER_TOKEN;
+ sb_in[0].cbBuffer = sizeof(token);
+ sb_in[0].pvBuffer = &token;
+ ApplyControlToken(&impl_->ctx, sb_in.desc());
+ // TODO: In theory, to do a nice shutdown, we need to begin shutdown
+ // negotiation with more calls to InitializeSecurityContext. Since the
+ // socket api doesn't support nice shutdown at this point, we don't bother.
+ }
+ Cleanup();
+ impl_ = new SSLImpl;
+ state_ = restartable_ ? SSL_WAIT : SSL_NONE;
+ signal_close_ = false;
+ message_pending_ = false;
+ return AsyncSocketAdapter::Close();
+}
+
+Socket::ConnState
+SChannelAdapter::GetState() const {
+ if (signal_close_)
+ return CS_CONNECTED;
+ ConnState state = socket_->GetState();
+ if ((state == CS_CONNECTED)
+ && ((state_ == SSL_WAIT) || (state_ == SSL_CONNECTING)))
+ state = CS_CONNECTING;
+ return state;
+}
+
+void
+SChannelAdapter::OnConnectEvent(AsyncSocket* socket) {
+ LOG(LS_VERBOSE) << "SChannelAdapter::OnConnectEvent";
+ if (state_ != SSL_WAIT) {
+ ASSERT(state_ == SSL_NONE);
+ AsyncSocketAdapter::OnConnectEvent(socket);
+ return;
+ }
+
+ state_ = SSL_CONNECTING;
+ if (int err = BeginSSL()) {
+ Error("BeginSSL", err);
+ }
+}
+
+void
+SChannelAdapter::OnReadEvent(AsyncSocket* socket) {
+ if (state_ == SSL_NONE) {
+ AsyncSocketAdapter::OnReadEvent(socket);
+ return;
+ }
+
+ if (int err = Read()) {
+ Error("Read", err);
+ return;
+ }
+
+ if (impl_->inbuf.empty())
+ return;
+
+ if (state_ == SSL_CONNECTED) {
+ if (int err = DecryptData()) {
+ Error("DecryptData", err);
+ } else if (!impl_->readable.empty()) {
+ AsyncSocketAdapter::OnReadEvent(this);
+ }
+ } else if (state_ == SSL_CONNECTING) {
+ if (int err = ContinueSSL()) {
+ Error("ContinueSSL", err);
+ }
+ }
+}
+
+void
+SChannelAdapter::OnWriteEvent(AsyncSocket* socket) {
+ if (state_ == SSL_NONE) {
+ AsyncSocketAdapter::OnWriteEvent(socket);
+ return;
+ }
+
+ if (int err = Flush()) {
+ Error("Flush", err);
+ return;
+ }
+
+ // See if we have more data to write
+ if (!impl_->outbuf.empty())
+ return;
+
+ // Buffer is empty, submit notification
+ if (state_ == SSL_CONNECTED) {
+ AsyncSocketAdapter::OnWriteEvent(socket);
+ }
+}
+
+void
+SChannelAdapter::OnCloseEvent(AsyncSocket* socket, int err) {
+ if ((state_ == SSL_NONE) || impl_->readable.empty()) {
+ AsyncSocketAdapter::OnCloseEvent(socket, err);
+ return;
+ }
+
+ // If readable is non-empty, then we have a pending Message
+ // that will allow us to signal close (eventually).
+ signal_close_ = true;
+}
+
+void
+SChannelAdapter::OnMessage(Message* pmsg) {
+ if (!message_pending_)
+ return; // This occurs when socket is closed
+
+ message_pending_ = false;
+ if (!impl_->readable.empty()) {
+ AsyncSocketAdapter::OnReadEvent(this);
+ } else if (signal_close_) {
+ signal_close_ = false;
+ AsyncSocketAdapter::OnCloseEvent(this, 0); // TODO: cache this error?
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/schanneladapter.h b/talk/base/schanneladapter.h
new file mode 100644
index 0000000..a5ab7b3
--- /dev/null
+++ b/talk/base/schanneladapter.h
@@ -0,0 +1,94 @@
+/*
+ * 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_SCHANNELADAPTER_H__
+#define TALK_BASE_SCHANNELADAPTER_H__
+
+#include <string>
+#include "talk/base/ssladapter.h"
+#include "talk/base/messagequeue.h"
+struct _SecBufferDesc;
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SChannelAdapter : public SSLAdapter, public MessageHandler {
+public:
+ SChannelAdapter(AsyncSocket* socket);
+ virtual ~SChannelAdapter();
+
+ virtual int StartSSL(const char* hostname, bool restartable);
+ virtual int Send(const void* pv, size_t cb);
+ virtual int Recv(void* pv, size_t cb);
+ virtual int Close();
+
+ // Note that the socket returns ST_CONNECTING while SSL is being negotiated.
+ virtual ConnState GetState() const;
+
+protected:
+ enum SSLState {
+ SSL_NONE, SSL_WAIT, SSL_CONNECTING, SSL_CONNECTED, SSL_ERROR
+ };
+ struct SSLImpl;
+
+ virtual void OnConnectEvent(AsyncSocket* socket);
+ virtual void OnReadEvent(AsyncSocket* socket);
+ virtual void OnWriteEvent(AsyncSocket* socket);
+ virtual void OnCloseEvent(AsyncSocket* socket, int err);
+ virtual void OnMessage(Message* pmsg);
+
+ int BeginSSL();
+ int ContinueSSL();
+ int ProcessContext(long int status, _SecBufferDesc* sbd_in,
+ _SecBufferDesc* sbd_out);
+ int DecryptData();
+
+ int Read();
+ int Flush();
+ void Error(const char* context, int err, bool signal = true);
+ void Cleanup();
+
+ void PostEvent();
+
+private:
+ SSLState state_;
+ std::string ssl_host_name_;
+ // If true, socket will retain SSL configuration after Close.
+ bool restartable_;
+ // If true, we are delaying signalling close until all data is read.
+ bool signal_close_;
+ // If true, we are waiting to be woken up to signal readability or closure.
+ bool message_pending_;
+ SSLImpl* impl_;
+};
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SCHANNELADAPTER_H__
diff --git a/talk/base/scoped_ptr.h b/talk/base/scoped_ptr.h
new file mode 100644
index 0000000..fd753ec
--- /dev/null
+++ b/talk/base/scoped_ptr.h
@@ -0,0 +1,258 @@
+// (C) Copyright Greg Colvin and Beman Dawes 1998, 1999.
+// Copyright (c) 2001, 2002 Peter Dimov
+//
+// Permission to copy, use, modify, sell and distribute this software
+// is granted provided this copyright notice appears in all copies.
+// This software is provided "as is" without express or implied
+// warranty, and with no claim as to its suitability for any purpose.
+//
+// See http://www.boost.org/libs/smart_ptr/scoped_ptr.htm for documentation.
+//
+
+// scoped_ptr mimics a built-in pointer except that it guarantees deletion
+// of the object pointed to, either on destruction of the scoped_ptr or via
+// an explicit reset(). scoped_ptr is a simple solution for simple needs;
+// use shared_ptr or std::auto_ptr if your needs are more complex.
+
+// scoped_ptr_malloc added in by Google. When one of
+// these goes out of scope, instead of doing a delete or delete[], it
+// calls free(). scoped_ptr_malloc<char> is likely to see much more
+// use than any other specializations.
+
+// release() added in by Google. Use this to conditionally
+// transfer ownership of a heap-allocated object to the caller, usually on
+// method success.
+#ifndef TALK_BASE_SCOPED_PTR_H__
+#define TALK_BASE_SCOPED_PTR_H__
+
+#include <cstddef> // for std::ptrdiff_t
+#include <stdlib.h> // for free() decl
+
+#include "talk/base/common.h" // for ASSERT
+
+#ifdef _WIN32
+namespace std { using ::ptrdiff_t; };
+#endif // _WIN32
+
+namespace talk_base {
+
+template <typename T>
+class scoped_ptr {
+ private:
+
+ T* ptr;
+
+ scoped_ptr(scoped_ptr const &);
+ scoped_ptr & operator=(scoped_ptr const &);
+
+ public:
+
+ typedef T element_type;
+
+ explicit scoped_ptr(T* p = NULL): ptr(p) {}
+
+ ~scoped_ptr() {
+ typedef char type_must_be_complete[sizeof(T)];
+ delete ptr;
+ }
+
+ void reset(T* p = NULL) {
+ typedef char type_must_be_complete[sizeof(T)];
+
+ if (ptr != p) {
+ T* obj = ptr;
+ ptr = p;
+ // Delete last, in case obj destructor indirectly results in ~scoped_ptr
+ delete obj;
+ }
+ }
+
+ T& operator*() const {
+ ASSERT(ptr != NULL);
+ return *ptr;
+ }
+
+ T* operator->() const {
+ ASSERT(ptr != NULL);
+ return ptr;
+ }
+
+ T* get() const {
+ return ptr;
+ }
+
+ void swap(scoped_ptr & b) {
+ T* tmp = b.ptr;
+ b.ptr = ptr;
+ ptr = tmp;
+ }
+
+ T* release() {
+ T* tmp = ptr;
+ ptr = NULL;
+ return tmp;
+ }
+
+ T** accept() {
+ if (ptr) {
+ delete ptr;
+ ptr = NULL;
+ }
+ return &ptr;
+ }
+
+ T** use() {
+ return &ptr;
+ }
+};
+
+template<typename T> inline
+void swap(scoped_ptr<T>& a, scoped_ptr<T>& b) {
+ a.swap(b);
+}
+
+
+
+
+// scoped_array extends scoped_ptr to arrays. Deletion of the array pointed to
+// is guaranteed, either on destruction of the scoped_array or via an explicit
+// reset(). Use shared_array or std::vector if your needs are more complex.
+
+template<typename T>
+class scoped_array {
+ private:
+
+ T* ptr;
+
+ scoped_array(scoped_array const &);
+ scoped_array & operator=(scoped_array const &);
+
+ public:
+
+ typedef T element_type;
+
+ explicit scoped_array(T* p = NULL) : ptr(p) {}
+
+ ~scoped_array() {
+ typedef char type_must_be_complete[sizeof(T)];
+ delete[] ptr;
+ }
+
+ void reset(T* p = NULL) {
+ typedef char type_must_be_complete[sizeof(T)];
+
+ if (ptr != p) {
+ T* arr = ptr;
+ ptr = p;
+ // Delete last, in case arr destructor indirectly results in ~scoped_array
+ delete [] arr;
+ }
+ }
+
+ T& operator[](std::ptrdiff_t i) const {
+ ASSERT(ptr != NULL);
+ ASSERT(i >= 0);
+ return ptr[i];
+ }
+
+ T* get() const {
+ return ptr;
+ }
+
+ void swap(scoped_array & b) {
+ T* tmp = b.ptr;
+ b.ptr = ptr;
+ ptr = tmp;
+ }
+
+ T* release() {
+ T* tmp = ptr;
+ ptr = NULL;
+ return tmp;
+ }
+
+ T** accept() {
+ if (ptr) {
+ delete [] ptr;
+ ptr = NULL;
+ }
+ return &ptr;
+ }
+};
+
+template<class T> inline
+void swap(scoped_array<T>& a, scoped_array<T>& b) {
+ a.swap(b);
+}
+
+// scoped_ptr_malloc<> is similar to scoped_ptr<>, but it accepts a
+// second template argument, the function used to free the object.
+
+template<typename T, void (*FF)(void*) = free> class scoped_ptr_malloc {
+ private:
+
+ T* ptr;
+
+ scoped_ptr_malloc(scoped_ptr_malloc const &);
+ scoped_ptr_malloc & operator=(scoped_ptr_malloc const &);
+
+ public:
+
+ typedef T element_type;
+
+ explicit scoped_ptr_malloc(T* p = 0): ptr(p) {}
+
+ ~scoped_ptr_malloc() {
+ FF(static_cast<void*>(ptr));
+ }
+
+ void reset(T* p = 0) {
+ if (ptr != p) {
+ FF(static_cast<void*>(ptr));
+ ptr = p;
+ }
+ }
+
+ T& operator*() const {
+ ASSERT(ptr != 0);
+ return *ptr;
+ }
+
+ T* operator->() const {
+ ASSERT(ptr != 0);
+ return ptr;
+ }
+
+ T* get() const {
+ return ptr;
+ }
+
+ void swap(scoped_ptr_malloc & b) {
+ T* tmp = b.ptr;
+ b.ptr = ptr;
+ ptr = tmp;
+ }
+
+ T* release() {
+ T* tmp = ptr;
+ ptr = 0;
+ return tmp;
+ }
+
+ T** accept() {
+ if (ptr) {
+ FF(static_cast<void*>(ptr));
+ ptr = 0;
+ }
+ return &ptr;
+ }
+};
+
+template<typename T, void (*FF)(void*)> inline
+void swap(scoped_ptr_malloc<T,FF>& a, scoped_ptr_malloc<T,FF>& b) {
+ a.swap(b);
+}
+
+} // namespace talk_base
+
+#endif // #ifndef TALK_BASE_SCOPED_PTR_H__
diff --git a/talk/base/sec_buffer.h b/talk/base/sec_buffer.h
new file mode 100644
index 0000000..585e27f
--- /dev/null
+++ b/talk/base/sec_buffer.h
@@ -0,0 +1,173 @@
+/*
+ * 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.
+ */
+
+// @file Contains utility classes that make it easier to use SecBuffers
+
+#ifndef TALK_BASE_SEC_BUFFER_H__
+#define TALK_BASE_SEC_BUFFER_H__
+
+namespace talk_base {
+
+// A base class for CSecBuffer<T>. Contains
+// all implementation that does not require
+// template arguments.
+class CSecBufferBase : public SecBuffer {
+ public:
+ CSecBufferBase() {
+ Clear();
+ }
+
+ // Uses the SSPI to free a pointer, must be
+ // used for buffers returned from SSPI APIs.
+ static void FreeSSPI(void *ptr) {
+ if ( ptr ) {
+ SECURITY_STATUS status;
+ status = ::FreeContextBuffer(ptr);
+ ASSERT(SEC_E_OK == status); // "Freeing context buffer"
+ }
+ }
+
+ // Deletes a buffer with operator delete
+ static void FreeDelete(void *ptr) {
+ delete [] reinterpret_cast<char*>(ptr);
+ }
+
+ // A noop delete, for buffers over other
+ // people's memory
+ static void FreeNone(void *ptr) {
+ }
+
+ protected:
+ // Clears the buffer to EMPTY & NULL
+ void Clear() {
+ this->BufferType = SECBUFFER_EMPTY;
+ this->cbBuffer = 0;
+ this->pvBuffer = NULL;
+ }
+};
+
+// Wrapper class for SecBuffer to take care
+// of initialization and destruction.
+template <void (*pfnFreeBuffer)(void *ptr)>
+class CSecBuffer: public CSecBufferBase {
+ public:
+ // Initializes buffer to empty & NULL
+ CSecBuffer() {
+ }
+
+ // Frees any allocated memory
+ ~CSecBuffer() {
+ Release();
+ }
+
+ // Frees the buffer appropriately, and re-nulls
+ void Release() {
+ pfnFreeBuffer(this->pvBuffer);
+ Clear();
+ }
+
+ private:
+ // A placeholder function for compile-time asserts on the class
+ void CompileAsserts() {
+ // never invoked...
+ assert(false); // _T("Notreached")
+
+ // This class must not extend the size of SecBuffer, since
+ // we use arrays of CSecBuffer in CSecBufferBundle below
+ cassert(sizeof(CSecBuffer<SSPIFree> == sizeof(SecBuffer)));
+ }
+};
+
+// Contains all generic implementation for the
+// SecBufferBundle class
+class SecBufferBundleBase {
+ public:
+};
+
+// A template class that bundles a SecBufferDesc with
+// one or more SecBuffers for convenience. Can take
+// care of deallocating buffers appropriately, as indicated
+// by pfnFreeBuffer function.
+// By default does no deallocation.
+template <int num_buffers,
+ void (*pfnFreeBuffer)(void *ptr) = CSecBufferBase::FreeNone>
+class CSecBufferBundle : public SecBufferBundleBase {
+ public:
+ // Constructs a security buffer bundle with num_buffers
+ // buffers, all of which are empty and nulled.
+ CSecBufferBundle() {
+ desc_.ulVersion = SECBUFFER_VERSION;
+ desc_.cBuffers = num_buffers;
+ desc_.pBuffers = buffers_;
+ }
+
+ // Frees all currently used buffers.
+ ~CSecBufferBundle() {
+ Release();
+ }
+
+ // Accessor for the descriptor
+ PSecBufferDesc desc() {
+ return &desc_;
+ }
+
+ // Accessor for the descriptor
+ const PSecBufferDesc desc() const {
+ return &desc_;
+ }
+
+ // returns the i-th security buffer
+ SecBuffer &operator[] (size_t num) {
+ ASSERT(num < num_buffers); // "Buffer index out of bounds"
+ return buffers_[num];
+ }
+
+ // returns the i-th security buffer
+ const SecBuffer &operator[] (size_t num) const {
+ ASSERT(num < num_buffers); // "Buffer index out of bounds"
+ return buffers_[num];
+ }
+
+ // Frees all non-NULL security buffers,
+ // using the deallocation function
+ void Release() {
+ for ( size_t i = 0; i < num_buffers; ++i ) {
+ buffers_[i].Release();
+ }
+ }
+
+ private:
+ // Our descriptor
+ SecBufferDesc desc_;
+ // Our bundled buffers, each takes care of its own
+ // initialization and destruction
+ CSecBuffer<pfnFreeBuffer> buffers_[num_buffers];
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SEC_BUFFER_H__
diff --git a/talk/base/signalthread.cc b/talk/base/signalthread.cc
new file mode 100644
index 0000000..d6a86c0
--- /dev/null
+++ b/talk/base/signalthread.cc
@@ -0,0 +1,164 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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/signalthread.h"
+
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread
+///////////////////////////////////////////////////////////////////////////////
+
+SignalThread::SignalThread() : main_(Thread::Current()), state_(kInit) {
+ main_->SignalQueueDestroyed.connect(this,
+ &SignalThread::OnMainThreadDestroyed);
+ refcount_ = 1;
+ worker_.parent_ = this;
+ worker_.SetName("SignalThread", this);
+}
+
+SignalThread::~SignalThread() {
+ ASSERT(refcount_ == 0);
+}
+
+bool SignalThread::SetName(const std::string& name, const void* obj) {
+ EnterExit ee(this);
+ ASSERT(main_->IsCurrent());
+ ASSERT(kInit == state_);
+ return worker_.SetName(name, obj);
+}
+
+bool SignalThread::SetPriority(ThreadPriority priority) {
+ EnterExit ee(this);
+ ASSERT(main_->IsCurrent());
+ ASSERT(kInit == state_);
+ return worker_.SetPriority(priority);
+}
+
+void SignalThread::Start() {
+ EnterExit ee(this);
+ ASSERT(main_->IsCurrent());
+ if (kInit == state_ || kComplete == state_) {
+ state_ = kRunning;
+ OnWorkStart();
+ worker_.Start();
+ } else {
+ ASSERT(false);
+ }
+}
+
+void SignalThread::Destroy(bool wait) {
+ EnterExit ee(this);
+ ASSERT(main_->IsCurrent());
+ if ((kInit == state_) || (kComplete == state_)) {
+ refcount_--;
+ } else if (kRunning == state_ || kReleasing == state_) {
+ state_ = kStopping;
+ // OnWorkStop() must follow Quit(), so that when the thread wakes up due to
+ // OWS(), ContinueWork() will return false.
+ worker_.Quit();
+ OnWorkStop();
+ if (wait) {
+ // Release the thread's lock so that it can return from ::Run.
+ cs_.Leave();
+ worker_.Stop();
+ cs_.Enter();
+ refcount_--;
+ }
+ } else {
+ ASSERT(false);
+ }
+}
+
+void SignalThread::Release() {
+ EnterExit ee(this);
+ ASSERT(main_->IsCurrent());
+ if (kComplete == state_) {
+ refcount_--;
+ } else if (kRunning == state_) {
+ state_ = kReleasing;
+ } else {
+ // if (kInit == state_) use Destroy()
+ ASSERT(false);
+ }
+}
+
+bool SignalThread::ContinueWork() {
+ EnterExit ee(this);
+ ASSERT(worker_.IsCurrent());
+ return worker_.ProcessMessages(0);
+}
+
+void SignalThread::OnMessage(Message *msg) {
+ EnterExit ee(this);
+ if (ST_MSG_WORKER_DONE == msg->message_id) {
+ ASSERT(main_->IsCurrent());
+ OnWorkDone();
+ bool do_delete = false;
+ if (kRunning == state_) {
+ state_ = kComplete;
+ } else {
+ do_delete = true;
+ }
+ if (kStopping != state_) {
+ // Before signaling that the work is done, make sure that the worker
+ // thread actually is done. We got here because DoWork() finished and
+ // Run() posted the ST_MSG_WORKER_DONE message. This means the worker
+ // thread is about to go away anyway, but sometimes it doesn't actually
+ // finish before SignalWorkDone is processed, and for a reusable
+ // SignalThread this makes an assert in thread.cc fire.
+ //
+ // Calling Stop() on the worker ensures that the OS thread that underlies
+ // the worker will finish, and will be set to NULL, enabling us to call
+ // Start() again.
+ worker_.Stop();
+ SignalWorkDone(this);
+ }
+ if (do_delete) {
+ refcount_--;
+ }
+ }
+}
+
+void SignalThread::Run() {
+ DoWork();
+ {
+ EnterExit ee(this);
+ if (main_) {
+ main_->Post(this, ST_MSG_WORKER_DONE);
+ }
+ }
+}
+
+void SignalThread::OnMainThreadDestroyed() {
+ EnterExit ee(this);
+ main_ = NULL;
+}
+
+} // namespace talk_base
diff --git a/talk/base/signalthread.h b/talk/base/signalthread.h
new file mode 100644
index 0000000..99a6e69
--- /dev/null
+++ b/talk/base/signalthread.h
@@ -0,0 +1,158 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_SIGNALTHREAD_H_
+#define TALK_BASE_SIGNALTHREAD_H_
+
+#include <string>
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SignalThread - Base class for worker threads. The main thread should call
+// Start() to begin work, and then follow one of these models:
+// Normal: Wait for SignalWorkDone, and then call Release to destroy.
+// Cancellation: Call Release(true), to abort the worker thread.
+// Fire-and-forget: Call Release(false), which allows the thread to run to
+// completion, and then self-destruct without further notification.
+// Periodic tasks: Wait for SignalWorkDone, then eventually call Start()
+// again to repeat the task. When the instance isn't needed anymore,
+// call Release. DoWork, OnWorkStart and OnWorkStop are called again,
+// on a new thread.
+// The subclass should override DoWork() to perform the background task. By
+// periodically calling ContinueWork(), it can check for cancellation.
+// OnWorkStart and OnWorkDone can be overridden to do pre- or post-work
+// tasks in the context of the main thread.
+///////////////////////////////////////////////////////////////////////////////
+
+class SignalThread : public sigslot::has_slots<>, protected MessageHandler {
+ public:
+ SignalThread();
+
+ // Context: Main Thread. Call before Start to change the worker's name.
+ bool SetName(const std::string& name, const void* obj);
+
+ // Context: Main Thread. Call before Start to change the worker's priority.
+ bool SetPriority(ThreadPriority priority);
+
+ // Context: Main Thread. Call to begin the worker thread.
+ void Start();
+
+ // Context: Main Thread. If the worker thread is not running, deletes the
+ // object immediately. Otherwise, asks the worker thread to abort processing,
+ // and schedules the object to be deleted once the worker exits.
+ // SignalWorkDone will not be signalled. If wait is true, does not return
+ // until the thread is deleted.
+ void Destroy(bool wait);
+
+ // Context: Main Thread. If the worker thread is complete, deletes the
+ // object immediately. Otherwise, schedules the object to be deleted once
+ // the worker thread completes. SignalWorkDone will be signalled.
+ void Release();
+
+ // Context: Main Thread. Signalled when work is complete.
+ sigslot::signal1<SignalThread *> SignalWorkDone;
+
+ enum { ST_MSG_WORKER_DONE, ST_MSG_FIRST_AVAILABLE };
+
+ protected:
+ virtual ~SignalThread();
+
+ Thread* worker() { return &worker_; }
+
+ // Context: Main Thread. Subclass should override to do pre-work setup.
+ virtual void OnWorkStart() { }
+
+ // Context: Worker Thread. Subclass should override to do work.
+ virtual void DoWork() = 0;
+
+ // Context: Worker Thread. Subclass should call periodically to
+ // dispatch messages and determine if the thread should terminate.
+ bool ContinueWork();
+
+ // Context: Worker Thread. Subclass should override when extra work is
+ // needed to abort the worker thread.
+ virtual void OnWorkStop() { }
+
+ // Context: Main Thread. Subclass should override to do post-work cleanup.
+ virtual void OnWorkDone() { }
+
+ // Context: Any Thread. If subclass overrides, be sure to call the base
+ // implementation. Do not use (message_id < ST_MSG_FIRST_AVAILABLE)
+ virtual void OnMessage(Message *msg);
+
+ private:
+ enum State {
+ kInit, // Initialized, but not started
+ kRunning, // Started and doing work
+ kReleasing, // Same as running, but to be deleted when work is done
+ kComplete, // Work is done
+ kStopping, // Work is being interrupted
+ };
+
+ friend class Worker;
+ class Worker : public Thread {
+ public:
+ SignalThread* parent_;
+ virtual void Run() { parent_->Run(); }
+ };
+
+ friend class EnterExit;
+ class EnterExit {
+ public:
+ explicit EnterExit(SignalThread* t) : t_(t) {
+ t_->cs_.Enter();
+ t_->refcount_ += 1;
+ }
+ ~EnterExit() {
+ bool d = (0 == (--(t_->refcount_)));
+ t_->cs_.Leave();
+ if (d)
+ delete t_;
+ }
+ private:
+ SignalThread* t_;
+ };
+
+ void Run();
+ void OnMainThreadDestroyed();
+
+ Thread* main_;
+ Worker worker_;
+ CriticalSection cs_;
+ State state_;
+ int refcount_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SIGNALTHREAD_H_
diff --git a/talk/base/sigslot.h b/talk/base/sigslot.h
new file mode 100644
index 0000000..e9b85c7
--- /dev/null
+++ b/talk/base/sigslot.h
@@ -0,0 +1,2816 @@
+// sigslot.h: Signal/Slot classes
+//
+// Written by Sarah Thompson (sarah@telergy.com) 2002.
+//
+// License: Public domain. You are free to use this code however you like, with the proviso that
+// the author takes on no responsibility or liability for any use.
+//
+// QUICK DOCUMENTATION
+//
+// (see also the full documentation at http://sigslot.sourceforge.net/)
+//
+// #define switches
+// SIGSLOT_PURE_ISO - Define this to force ISO C++ compliance. This also disables
+// all of the thread safety support on platforms where it is
+// available.
+//
+// SIGSLOT_USE_POSIX_THREADS - Force use of Posix threads when using a C++ compiler other than
+// gcc on a platform that supports Posix threads. (When using gcc,
+// this is the default - use SIGSLOT_PURE_ISO to disable this if
+// necessary)
+//
+// SIGSLOT_DEFAULT_MT_POLICY - Where thread support is enabled, this defaults to multi_threaded_global.
+// Otherwise, the default is single_threaded. #define this yourself to
+// override the default. In pure ISO mode, anything other than
+// single_threaded will cause a compiler error.
+//
+// PLATFORM NOTES
+//
+// Win32 - On Win32, the WIN32 symbol must be #defined. Most mainstream
+// compilers do this by default, but you may need to define it
+// yourself if your build environment is less standard. This causes
+// the Win32 thread support to be compiled in and used automatically.
+//
+// Unix/Linux/BSD, etc. - If you're using gcc, it is assumed that you have Posix threads
+// available, so they are used automatically. You can override this
+// (as under Windows) with the SIGSLOT_PURE_ISO switch. If you're using
+// something other than gcc but still want to use Posix threads, you
+// need to #define SIGSLOT_USE_POSIX_THREADS.
+//
+// ISO C++ - If none of the supported platforms are detected, or if
+// SIGSLOT_PURE_ISO is defined, all multithreading support is turned off,
+// along with any code that might cause a pure ISO C++ environment to
+// complain. Before you ask, gcc -ansi -pedantic won't compile this
+// library, but gcc -ansi is fine. Pedantic mode seems to throw a lot of
+// errors that aren't really there. If you feel like investigating this,
+// please contact the author.
+//
+//
+// THREADING MODES
+//
+// single_threaded - Your program is assumed to be single threaded from the point of view
+// of signal/slot usage (i.e. all objects using signals and slots are
+// created and destroyed from a single thread). Behaviour if objects are
+// destroyed concurrently is undefined (i.e. you'll get the occasional
+// segmentation fault/memory exception).
+//
+// multi_threaded_global - Your program is assumed to be multi threaded. Objects using signals and
+// slots can be safely created and destroyed from any thread, even when
+// connections exist. In multi_threaded_global mode, this is achieved by a
+// single global mutex (actually a critical section on Windows because they
+// are faster). This option uses less OS resources, but results in more
+// opportunities for contention, possibly resulting in more context switches
+// than are strictly necessary.
+//
+// multi_threaded_local - Behaviour in this mode is essentially the same as multi_threaded_global,
+// except that each signal, and each object that inherits has_slots, all
+// have their own mutex/critical section. In practice, this means that
+// mutex collisions (and hence context switches) only happen if they are
+// absolutely essential. However, on some platforms, creating a lot of
+// mutexes can slow down the whole OS, so use this option with care.
+//
+// USING THE LIBRARY
+//
+// See the full documentation at http://sigslot.sourceforge.net/
+//
+//
+
+#ifndef TALK_BASE_SIGSLOT_H__
+#define TALK_BASE_SIGSLOT_H__
+
+#include <set>
+#include <list>
+
+// On our copy of sigslot.h, we force single threading
+#define SIGSLOT_PURE_ISO
+
+#if defined(SIGSLOT_PURE_ISO) || (!defined(WIN32) && !defined(__GNUG__) && !defined(SIGSLOT_USE_POSIX_THREADS))
+# define _SIGSLOT_SINGLE_THREADED
+#elif defined(WIN32)
+# define _SIGSLOT_HAS_WIN32_THREADS
+# include <windows.h>
+#elif defined(__GNUG__) || defined(SIGSLOT_USE_POSIX_THREADS)
+# define _SIGSLOT_HAS_POSIX_THREADS
+# include <pthread.h>
+#else
+# define _SIGSLOT_SINGLE_THREADED
+#endif
+
+#ifndef SIGSLOT_DEFAULT_MT_POLICY
+# ifdef _SIGSLOT_SINGLE_THREADED
+# define SIGSLOT_DEFAULT_MT_POLICY single_threaded
+# else
+# define SIGSLOT_DEFAULT_MT_POLICY multi_threaded_local
+# endif
+#endif
+
+// TODO: change this namespace to talk_base?
+namespace sigslot {
+
+ class single_threaded
+ {
+ public:
+ single_threaded()
+ {
+ ;
+ }
+
+ virtual ~single_threaded()
+ {
+ ;
+ }
+
+ virtual void lock()
+ {
+ ;
+ }
+
+ virtual void unlock()
+ {
+ ;
+ }
+ };
+
+#ifdef _SIGSLOT_HAS_WIN32_THREADS
+ // The multi threading policies only get compiled in if they are enabled.
+ class multi_threaded_global
+ {
+ public:
+ multi_threaded_global()
+ {
+ static bool isinitialised = false;
+
+ if(!isinitialised)
+ {
+ InitializeCriticalSection(get_critsec());
+ isinitialised = true;
+ }
+ }
+
+ multi_threaded_global(const multi_threaded_global&)
+ {
+ ;
+ }
+
+ virtual ~multi_threaded_global()
+ {
+ ;
+ }
+
+ virtual void lock()
+ {
+ EnterCriticalSection(get_critsec());
+ }
+
+ virtual void unlock()
+ {
+ LeaveCriticalSection(get_critsec());
+ }
+
+ private:
+ CRITICAL_SECTION* get_critsec()
+ {
+ static CRITICAL_SECTION g_critsec;
+ return &g_critsec;
+ }
+ };
+
+ class multi_threaded_local
+ {
+ public:
+ multi_threaded_local()
+ {
+ InitializeCriticalSection(&m_critsec);
+ }
+
+ multi_threaded_local(const multi_threaded_local&)
+ {
+ InitializeCriticalSection(&m_critsec);
+ }
+
+ virtual ~multi_threaded_local()
+ {
+ DeleteCriticalSection(&m_critsec);
+ }
+
+ virtual void lock()
+ {
+ EnterCriticalSection(&m_critsec);
+ }
+
+ virtual void unlock()
+ {
+ LeaveCriticalSection(&m_critsec);
+ }
+
+ private:
+ CRITICAL_SECTION m_critsec;
+ };
+#endif // _SIGSLOT_HAS_WIN32_THREADS
+
+#ifdef _SIGSLOT_HAS_POSIX_THREADS
+ // The multi threading policies only get compiled in if they are enabled.
+ class multi_threaded_global
+ {
+ public:
+ multi_threaded_global()
+ {
+ pthread_mutex_init(get_mutex(), NULL);
+ }
+
+ multi_threaded_global(const multi_threaded_global&)
+ {
+ ;
+ }
+
+ virtual ~multi_threaded_global()
+ {
+ ;
+ }
+
+ virtual void lock()
+ {
+ pthread_mutex_lock(get_mutex());
+ }
+
+ virtual void unlock()
+ {
+ pthread_mutex_unlock(get_mutex());
+ }
+
+ private:
+ pthread_mutex_t* get_mutex()
+ {
+ static pthread_mutex_t g_mutex;
+ return &g_mutex;
+ }
+ };
+
+ class multi_threaded_local
+ {
+ public:
+ multi_threaded_local()
+ {
+ pthread_mutex_init(&m_mutex, NULL);
+ }
+
+ multi_threaded_local(const multi_threaded_local&)
+ {
+ pthread_mutex_init(&m_mutex, NULL);
+ }
+
+ virtual ~multi_threaded_local()
+ {
+ pthread_mutex_destroy(&m_mutex);
+ }
+
+ virtual void lock()
+ {
+ pthread_mutex_lock(&m_mutex);
+ }
+
+ virtual void unlock()
+ {
+ pthread_mutex_unlock(&m_mutex);
+ }
+
+ private:
+ pthread_mutex_t m_mutex;
+ };
+#endif // _SIGSLOT_HAS_POSIX_THREADS
+
+ template<class mt_policy>
+ class lock_block
+ {
+ public:
+ mt_policy *m_mutex;
+
+ lock_block(mt_policy *mtx)
+ : m_mutex(mtx)
+ {
+ m_mutex->lock();
+ }
+
+ ~lock_block()
+ {
+ m_mutex->unlock();
+ }
+ };
+
+ template<class mt_policy>
+ class has_slots;
+
+ template<class mt_policy>
+ class _connection_base0
+ {
+ public:
+ virtual ~_connection_base0() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit() = 0;
+ virtual _connection_base0* clone() = 0;
+ virtual _connection_base0* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class mt_policy>
+ class _connection_base1
+ {
+ public:
+ virtual ~_connection_base1() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type) = 0;
+ virtual _connection_base1<arg1_type, mt_policy>* clone() = 0;
+ virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class mt_policy>
+ class _connection_base2
+ {
+ public:
+ virtual ~_connection_base2() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type) = 0;
+ virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone() = 0;
+ virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+ class _connection_base3
+ {
+ public:
+ virtual ~_connection_base3() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type) = 0;
+ virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone() = 0;
+ virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+ class _connection_base4
+ {
+ public:
+ virtual ~_connection_base4() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type) = 0;
+ virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone() = 0;
+ virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class mt_policy>
+ class _connection_base5
+ {
+ public:
+ virtual ~_connection_base5() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type) = 0;
+ virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>* clone() = 0;
+ virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class mt_policy>
+ class _connection_base6
+ {
+ public:
+ virtual ~_connection_base6() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+ arg6_type) = 0;
+ virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>* clone() = 0;
+ virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+ class _connection_base7
+ {
+ public:
+ virtual ~_connection_base7() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+ arg6_type, arg7_type) = 0;
+ virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>* clone() = 0;
+ virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+ class _connection_base8
+ {
+ public:
+ virtual ~_connection_base8() {}
+ virtual has_slots<mt_policy>* getdest() const = 0;
+ virtual void emit(arg1_type, arg2_type, arg3_type, arg4_type, arg5_type,
+ arg6_type, arg7_type, arg8_type) = 0;
+ virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone() = 0;
+ virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest) = 0;
+ };
+
+ template<class mt_policy>
+ class _signal_base : public mt_policy
+ {
+ public:
+ virtual void slot_disconnect(has_slots<mt_policy>* pslot) = 0;
+ virtual void slot_duplicate(const has_slots<mt_policy>* poldslot, has_slots<mt_policy>* pnewslot) = 0;
+ };
+
+ template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class has_slots : public mt_policy
+ {
+ private:
+ typedef typename std::set<_signal_base<mt_policy> *> sender_set;
+ typedef typename sender_set::const_iterator const_iterator;
+
+ public:
+ has_slots()
+ {
+ ;
+ }
+
+ has_slots(const has_slots& hs)
+ : mt_policy(hs)
+ {
+ lock_block<mt_policy> lock(this);
+ const_iterator it = hs.m_senders.begin();
+ const_iterator itEnd = hs.m_senders.end();
+
+ while(it != itEnd)
+ {
+ (*it)->slot_duplicate(&hs, this);
+ m_senders.insert(*it);
+ ++it;
+ }
+ }
+
+ void signal_connect(_signal_base<mt_policy>* sender)
+ {
+ lock_block<mt_policy> lock(this);
+ m_senders.insert(sender);
+ }
+
+ void signal_disconnect(_signal_base<mt_policy>* sender)
+ {
+ lock_block<mt_policy> lock(this);
+ m_senders.erase(sender);
+ }
+
+ virtual ~has_slots()
+ {
+ disconnect_all();
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ const_iterator it = m_senders.begin();
+ const_iterator itEnd = m_senders.end();
+
+ while(it != itEnd)
+ {
+ (*it)->slot_disconnect(this);
+ ++it;
+ }
+
+ m_senders.erase(m_senders.begin(), m_senders.end());
+ }
+
+ private:
+ sender_set m_senders;
+ };
+
+ template<class mt_policy>
+ class _signal_base0 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base0<mt_policy> *> connections_list;
+
+ _signal_base0()
+ {
+ ;
+ }
+
+ _signal_base0(const _signal_base0& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ ~_signal_base0()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class mt_policy>
+ class _signal_base1 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base1<arg1_type, mt_policy> *> connections_list;
+
+ _signal_base1()
+ {
+ ;
+ }
+
+ _signal_base1(const _signal_base1<arg1_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base1()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class mt_policy>
+ class _signal_base2 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base2<arg1_type, arg2_type, mt_policy> *>
+ connections_list;
+
+ _signal_base2()
+ {
+ ;
+ }
+
+ _signal_base2(const _signal_base2<arg1_type, arg2_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base2()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+ class _signal_base3 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base3<arg1_type, arg2_type, arg3_type, mt_policy> *>
+ connections_list;
+
+ _signal_base3()
+ {
+ ;
+ }
+
+ _signal_base3(const _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base3()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy>
+ class _signal_base4 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base4<arg1_type, arg2_type, arg3_type,
+ arg4_type, mt_policy> *> connections_list;
+
+ _signal_base4()
+ {
+ ;
+ }
+
+ _signal_base4(const _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base4()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class mt_policy>
+ class _signal_base5 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base5<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, mt_policy> *> connections_list;
+
+ _signal_base5()
+ {
+ ;
+ }
+
+ _signal_base5(const _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base5()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class mt_policy>
+ class _signal_base6 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base6<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, mt_policy> *> connections_list;
+
+ _signal_base6()
+ {
+ ;
+ }
+
+ _signal_base6(const _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base6()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+ class _signal_base7 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base7<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type, mt_policy> *> connections_list;
+
+ _signal_base7()
+ {
+ ;
+ }
+
+ _signal_base7(const _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base7()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy>
+ class _signal_base8 : public _signal_base<mt_policy>
+ {
+ public:
+ typedef std::list<_connection_base8<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> *>
+ connections_list;
+
+ _signal_base8()
+ {
+ ;
+ }
+
+ _signal_base8(const _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+ : _signal_base<mt_policy>(s)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = s.m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = s.m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_connect(this);
+ m_connected_slots.push_back((*it)->clone());
+
+ ++it;
+ }
+ }
+
+ void slot_duplicate(const has_slots<mt_policy>* oldtarget, has_slots<mt_policy>* newtarget)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == oldtarget)
+ {
+ m_connected_slots.push_back((*it)->duplicate(newtarget));
+ }
+
+ ++it;
+ }
+ }
+
+ ~_signal_base8()
+ {
+ disconnect_all();
+ }
+
+ bool is_empty()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ return it == itEnd;
+ }
+
+ void disconnect_all()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ (*it)->getdest()->signal_disconnect(this);
+ delete *it;
+
+ ++it;
+ }
+
+ m_connected_slots.erase(m_connected_slots.begin(), m_connected_slots.end());
+ }
+
+#ifdef _DEBUG
+ bool connected(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+ if ((*it)->getdest() == pclass)
+ return true;
+ it = itNext;
+ }
+ return false;
+ }
+#endif
+
+ void disconnect(has_slots<mt_policy>* pclass)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ if((*it)->getdest() == pclass)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ pclass->signal_disconnect(this);
+ return;
+ }
+
+ ++it;
+ }
+ }
+
+ void slot_disconnect(has_slots<mt_policy>* pslot)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::iterator it = m_connected_slots.begin();
+ typename connections_list::iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ typename connections_list::iterator itNext = it;
+ ++itNext;
+
+ if((*it)->getdest() == pslot)
+ {
+ delete *it;
+ m_connected_slots.erase(it);
+ }
+
+ it = itNext;
+ }
+ }
+
+ protected:
+ connections_list m_connected_slots;
+ };
+
+
+ template<class dest_type, class mt_policy>
+ class _connection0 : public _connection_base0<mt_policy>
+ {
+ public:
+ _connection0()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection0(dest_type* pobject, void (dest_type::*pmemfun)())
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection0()
+ {
+ }
+
+ virtual _connection_base0<mt_policy>* clone()
+ {
+ return new _connection0<dest_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base0<mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection0<dest_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit()
+ {
+ (m_pobject->*m_pmemfun)();
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)();
+ };
+
+ template<class dest_type, class arg1_type, class mt_policy>
+ class _connection1 : public _connection_base1<arg1_type, mt_policy>
+ {
+ public:
+ _connection1()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection1(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection1()
+ {
+ }
+
+ virtual _connection_base1<arg1_type, mt_policy>* clone()
+ {
+ return new _connection1<dest_type, arg1_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base1<arg1_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection1<dest_type, arg1_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1)
+ {
+ (m_pobject->*m_pmemfun)(a1);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class mt_policy>
+ class _connection2 : public _connection_base2<arg1_type, arg2_type, mt_policy>
+ {
+ public:
+ _connection2()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection2(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection2()
+ {
+ }
+
+ virtual _connection_base2<arg1_type, arg2_type, mt_policy>* clone()
+ {
+ return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base2<arg1_type, arg2_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection2<dest_type, arg1_type, arg2_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type, class mt_policy>
+ class _connection3 : public _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+ {
+ public:
+ _connection3()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection3(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection3()
+ {
+ }
+
+ virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* clone()
+ {
+ return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base3<arg1_type, arg2_type, arg3_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection3<dest_type, arg1_type, arg2_type, arg3_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+ class arg4_type, class mt_policy>
+ class _connection4 : public _connection_base4<arg1_type, arg2_type,
+ arg3_type, arg4_type, mt_policy>
+ {
+ public:
+ _connection4()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection4(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection4()
+ {
+ }
+
+ virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* clone()
+ {
+ return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection4<dest_type, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3,
+ arg4_type a4)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3, a4);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type,
+ arg4_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+ class arg4_type, class arg5_type, class mt_policy>
+ class _connection5 : public _connection_base5<arg1_type, arg2_type,
+ arg3_type, arg4_type, arg5_type, mt_policy>
+ {
+ public:
+ _connection5()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection5(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection5()
+ {
+ }
+
+ virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>* clone()
+ {
+ return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection5<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+ class arg4_type, class arg5_type, class arg6_type, class mt_policy>
+ class _connection6 : public _connection_base6<arg1_type, arg2_type,
+ arg3_type, arg4_type, arg5_type, arg6_type, mt_policy>
+ {
+ public:
+ _connection6()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection6(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection6()
+ {
+ }
+
+ virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>* clone()
+ {
+ return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection6<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+ class arg4_type, class arg5_type, class arg6_type, class arg7_type, class mt_policy>
+ class _connection7 : public _connection_base7<arg1_type, arg2_type,
+ arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+ {
+ public:
+ _connection7()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection7(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, arg7_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection7()
+ {
+ }
+
+ virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>* clone()
+ {
+ return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection7<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type);
+ };
+
+ template<class dest_type, class arg1_type, class arg2_type, class arg3_type,
+ class arg4_type, class arg5_type, class arg6_type, class arg7_type,
+ class arg8_type, class mt_policy>
+ class _connection8 : public _connection_base8<arg1_type, arg2_type,
+ arg3_type, arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+ {
+ public:
+ _connection8()
+ {
+ m_pobject = NULL;
+ m_pmemfun = NULL;
+ }
+
+ _connection8(dest_type* pobject, void (dest_type::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+ arg7_type, arg8_type))
+ {
+ m_pobject = pobject;
+ m_pmemfun = pmemfun;
+ }
+
+ virtual ~_connection8()
+ {
+ }
+
+ virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* clone()
+ {
+ return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(*this);
+ }
+
+ virtual _connection_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* duplicate(has_slots<mt_policy>* pnewdest)
+ {
+ return new _connection8<dest_type, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>((dest_type *)pnewdest, m_pmemfun);
+ }
+
+ virtual void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+ {
+ (m_pobject->*m_pmemfun)(a1, a2, a3, a4, a5, a6, a7, a8);
+ }
+
+ virtual has_slots<mt_policy>* getdest() const
+ {
+ return m_pobject;
+ }
+
+ private:
+ dest_type* m_pobject;
+ void (dest_type::* m_pmemfun)(arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type);
+ };
+
+ template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal0 : public _signal_base0<mt_policy>
+ {
+ public:
+ typedef _signal_base0<mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal0()
+ {
+ ;
+ }
+
+ signal0(const signal0<mt_policy>& s)
+ : _signal_base0<mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)())
+ {
+ lock_block<mt_policy> lock(this);
+ _connection0<desttype, mt_policy>* conn =
+ new _connection0<desttype, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit();
+
+ it = itNext;
+ }
+ }
+
+ void operator()()
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit();
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal1 : public _signal_base1<arg1_type, mt_policy>
+ {
+ public:
+ typedef _signal_base1<arg1_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal1()
+ {
+ ;
+ }
+
+ signal1(const signal1<arg1_type, mt_policy>& s)
+ : _signal_base1<arg1_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection1<desttype, arg1_type, mt_policy>* conn =
+ new _connection1<desttype, arg1_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal2 : public _signal_base2<arg1_type, arg2_type, mt_policy>
+ {
+ public:
+ typedef _signal_base2<arg1_type, arg2_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal2()
+ {
+ ;
+ }
+
+ signal2(const signal2<arg1_type, arg2_type, mt_policy>& s)
+ : _signal_base2<arg1_type, arg2_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection2<desttype, arg1_type, arg2_type, mt_policy>* conn = new
+ _connection2<desttype, arg1_type, arg2_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal3 : public _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>
+ {
+ public:
+ typedef _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal3()
+ {
+ ;
+ }
+
+ signal3(const signal3<arg1_type, arg2_type, arg3_type, mt_policy>& s)
+ : _signal_base3<arg1_type, arg2_type, arg3_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>* conn =
+ new _connection3<desttype, arg1_type, arg2_type, arg3_type, mt_policy>(pclass,
+ pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal4 : public _signal_base4<arg1_type, arg2_type, arg3_type,
+ arg4_type, mt_policy>
+ {
+ public:
+ typedef _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal4()
+ {
+ ;
+ }
+
+ signal4(const signal4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>& s)
+ : _signal_base4<arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection4<desttype, arg1_type, arg2_type, arg3_type, arg4_type, mt_policy>*
+ conn = new _connection4<desttype, arg1_type, arg2_type, arg3_type,
+ arg4_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal5 : public _signal_base5<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, mt_policy>
+ {
+ public:
+ typedef _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal5()
+ {
+ ;
+ }
+
+ signal5(const signal5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>& s)
+ : _signal_base5<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection5<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, mt_policy>* conn = new _connection5<desttype, arg1_type, arg2_type,
+ arg3_type, arg4_type, arg5_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5);
+
+ it = itNext;
+ }
+ }
+ };
+
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal6 : public _signal_base6<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, mt_policy>
+ {
+ public:
+ typedef _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type, arg5_type, arg6_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal6()
+ {
+ ;
+ }
+
+ signal6(const signal6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>& s)
+ : _signal_base6<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection6<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, mt_policy>* conn =
+ new _connection6<desttype, arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal7 : public _signal_base7<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>
+ {
+ public:
+ typedef _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal7()
+ {
+ ;
+ }
+
+ signal7(const signal7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>& s)
+ : _signal_base7<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+ arg7_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection7<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, mt_policy>* conn =
+ new _connection7<desttype, arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6, a7);
+
+ it = itNext;
+ }
+ }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type, class arg4_type,
+ class arg5_type, class arg6_type, class arg7_type, class arg8_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class signal8 : public _signal_base8<arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>
+ {
+ public:
+ typedef _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy> base;
+ typedef typename base::connections_list connections_list;
+ using base::m_connected_slots;
+
+ signal8()
+ {
+ ;
+ }
+
+ signal8(const signal8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>& s)
+ : _signal_base8<arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>(s)
+ {
+ ;
+ }
+
+ template<class desttype>
+ void connect(desttype* pclass, void (desttype::*pmemfun)(arg1_type,
+ arg2_type, arg3_type, arg4_type, arg5_type, arg6_type,
+ arg7_type, arg8_type))
+ {
+ lock_block<mt_policy> lock(this);
+ _connection8<desttype, arg1_type, arg2_type, arg3_type, arg4_type,
+ arg5_type, arg6_type, arg7_type, arg8_type, mt_policy>* conn =
+ new _connection8<desttype, arg1_type, arg2_type, arg3_type,
+ arg4_type, arg5_type, arg6_type, arg7_type,
+ arg8_type, mt_policy>(pclass, pmemfun);
+ m_connected_slots.push_back(conn);
+ pclass->signal_connect(this);
+ }
+
+ void emit(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+ it = itNext;
+ }
+ }
+
+ void operator()(arg1_type a1, arg2_type a2, arg3_type a3, arg4_type a4,
+ arg5_type a5, arg6_type a6, arg7_type a7, arg8_type a8)
+ {
+ lock_block<mt_policy> lock(this);
+ typename connections_list::const_iterator itNext, it = m_connected_slots.begin();
+ typename connections_list::const_iterator itEnd = m_connected_slots.end();
+
+ while(it != itEnd)
+ {
+ itNext = it;
+ ++itNext;
+
+ (*it)->emit(a1, a2, a3, a4, a5, a6, a7, a8);
+
+ it = itNext;
+ }
+ }
+ };
+
+}; // namespace sigslot
+
+#endif // TALK_BASE_SIGSLOT_H__
diff --git a/talk/base/sigslotrepeater.h b/talk/base/sigslotrepeater.h
new file mode 100644
index 0000000..3bcdc95
--- /dev/null
+++ b/talk/base/sigslotrepeater.h
@@ -0,0 +1,107 @@
+/*
+ * libjingle
+ * Copyright 2006, 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_SIGSLOTREPEATER_H__
+#define TALK_BASE_SIGSLOTREPEATER_H__
+
+// repeaters are both signals and slots, which are designed as intermediate
+// pass-throughs for signals and slots which don't know about each other (for
+// modularity or encapsulation). This eliminates the need to declare a signal
+// handler whose sole purpose is to fire another signal. The repeater connects
+// to the originating signal using the 'repeat' method. When the repeated
+// signal fires, the repeater will also fire.
+
+#include "talk/base/sigslot.h"
+
+namespace sigslot {
+
+ template<class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater0 : public signal0<mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal0<mt_policy> base_type;
+ typedef repeater0<mt_policy> this_type;
+
+ repeater0() { }
+ repeater0(const this_type& s) : base_type(s) { }
+
+ void reemit() { signal0<mt_policy>::emit(); }
+ void repeat(base_type &s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater1 : public signal1<arg1_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal1<arg1_type, mt_policy> base_type;
+ typedef repeater1<arg1_type, mt_policy> this_type;
+
+ repeater1() { }
+ repeater1(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1) { signal1<arg1_type, mt_policy>::emit(a1); }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class arg2_type, class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater2 : public signal2<arg1_type, arg2_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal2<arg1_type, arg2_type, mt_policy> base_type;
+ typedef repeater2<arg1_type, arg2_type, mt_policy> this_type;
+
+ repeater2() { }
+ repeater2(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2) { signal2<arg1_type, arg2_type, mt_policy>::emit(a1,a2); }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+ template<class arg1_type, class arg2_type, class arg3_type,
+ class mt_policy = SIGSLOT_DEFAULT_MT_POLICY>
+ class repeater3 : public signal3<arg1_type, arg2_type, arg3_type, mt_policy>,
+ public has_slots<mt_policy>
+ {
+ public:
+ typedef signal3<arg1_type, arg2_type, arg3_type, mt_policy> base_type;
+ typedef repeater3<arg1_type, arg2_type, arg3_type, mt_policy> this_type;
+
+ repeater3() { }
+ repeater3(const this_type& s) : base_type(s) { }
+
+ void reemit(arg1_type a1, arg2_type a2, arg3_type a3) {
+ signal3<arg1_type, arg2_type, arg3_type, mt_policy>::emit(a1,a2,a3);
+ }
+ void repeat(base_type& s) { s.connect(this, &this_type::reemit); }
+ };
+
+} // namespace sigslot
+
+#endif // TALK_BASE_SIGSLOTREPEATER_H__
diff --git a/talk/base/socket.h b/talk/base/socket.h
new file mode 100644
index 0000000..a55b3dc
--- /dev/null
+++ b/talk/base/socket.h
@@ -0,0 +1,199 @@
+/*
+ * 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_SOCKET_H__
+#define TALK_BASE_SOCKET_H__
+
+#include <errno.h>
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#define SOCKET_EACCES EACCES
+#endif
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+#include "talk/base/basictypes.h"
+#include "talk/base/socketaddress.h"
+
+// Rather than converting errors into a private namespace,
+// Reuse the POSIX socket api errors. Note this depends on
+// Win32 compatibility.
+
+#ifdef WIN32
+#undef EWOULDBLOCK // Remove errno.h's definition for each macro below.
+#define EWOULDBLOCK WSAEWOULDBLOCK
+#undef EINPROGRESS
+#define EINPROGRESS WSAEINPROGRESS
+#undef EALREADY
+#define EALREADY WSAEALREADY
+#undef ENOTSOCK
+#define ENOTSOCK WSAENOTSOCK
+#undef EDESTADDRREQ
+#define EDESTADDRREQ WSAEDESTADDRREQ
+#undef EMSGSIZE
+#define EMSGSIZE WSAEMSGSIZE
+#undef EPROTOTYPE
+#define EPROTOTYPE WSAEPROTOTYPE
+#undef ENOPROTOOPT
+#define ENOPROTOOPT WSAENOPROTOOPT
+#undef EPROTONOSUPPORT
+#define EPROTONOSUPPORT WSAEPROTONOSUPPORT
+#undef ESOCKTNOSUPPORT
+#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
+#undef EOPNOTSUPP
+#define EOPNOTSUPP WSAEOPNOTSUPP
+#undef EPFNOSUPPORT
+#define EPFNOSUPPORT WSAEPFNOSUPPORT
+#undef EAFNOSUPPORT
+#define EAFNOSUPPORT WSAEAFNOSUPPORT
+#undef EADDRINUSE
+#define EADDRINUSE WSAEADDRINUSE
+#undef EADDRNOTAVAIL
+#define EADDRNOTAVAIL WSAEADDRNOTAVAIL
+#undef ENETDOWN
+#define ENETDOWN WSAENETDOWN
+#undef ENETUNREACH
+#define ENETUNREACH WSAENETUNREACH
+#undef ENETRESET
+#define ENETRESET WSAENETRESET
+#undef ECONNABORTED
+#define ECONNABORTED WSAECONNABORTED
+#undef ECONNRESET
+#define ECONNRESET WSAECONNRESET
+#undef ENOBUFS
+#define ENOBUFS WSAENOBUFS
+#undef EISCONN
+#define EISCONN WSAEISCONN
+#undef ENOTCONN
+#define ENOTCONN WSAENOTCONN
+#undef ESHUTDOWN
+#define ESHUTDOWN WSAESHUTDOWN
+#undef ETOOMANYREFS
+#define ETOOMANYREFS WSAETOOMANYREFS
+#undef ETIMEDOUT
+#define ETIMEDOUT WSAETIMEDOUT
+#undef ECONNREFUSED
+#define ECONNREFUSED WSAECONNREFUSED
+#undef ELOOP
+#define ELOOP WSAELOOP
+#undef ENAMETOOLONG
+#define ENAMETOOLONG WSAENAMETOOLONG
+#undef EHOSTDOWN
+#define EHOSTDOWN WSAEHOSTDOWN
+#undef EHOSTUNREACH
+#define EHOSTUNREACH WSAEHOSTUNREACH
+#undef ENOTEMPTY
+#define ENOTEMPTY WSAENOTEMPTY
+#undef EPROCLIM
+#define EPROCLIM WSAEPROCLIM
+#undef EUSERS
+#define EUSERS WSAEUSERS
+#undef EDQUOT
+#define EDQUOT WSAEDQUOT
+#undef ESTALE
+#define ESTALE WSAESTALE
+#undef EREMOTE
+#define EREMOTE WSAEREMOTE
+#undef EACCES
+#define SOCKET_EACCES WSAEACCES
+#endif // WIN32
+
+#ifdef POSIX
+#define INVALID_SOCKET (-1)
+#define SOCKET_ERROR (-1)
+#define closesocket(s) close(s)
+#endif // POSIX
+
+namespace talk_base {
+
+inline bool IsBlockingError(int e) {
+ return (e == EWOULDBLOCK) || (e == EAGAIN) || (e == EINPROGRESS);
+}
+
+// General interface for the socket implementations of various networks. The
+// methods match those of normal UNIX sockets very closely.
+class Socket {
+ public:
+ virtual ~Socket() {}
+
+ // Returns the address to which the socket is bound. If the socket is not
+ // bound, then the any-address is returned.
+ virtual SocketAddress GetLocalAddress() const = 0;
+
+ // Returns the address to which the socket is connected. If the socket is
+ // not connected, then the any-address is returned.
+ virtual SocketAddress GetRemoteAddress() const = 0;
+
+ virtual int Bind(const SocketAddress& addr) = 0;
+ virtual int Connect(const SocketAddress& addr) = 0;
+ virtual int Send(const void *pv, size_t cb) = 0;
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr) = 0;
+ virtual int Recv(void *pv, size_t cb) = 0;
+ virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) = 0;
+ virtual int Listen(int backlog) = 0;
+ virtual Socket *Accept(SocketAddress *paddr) = 0;
+ virtual int Close() = 0;
+ virtual int GetError() const = 0;
+ virtual void SetError(int error) = 0;
+ inline bool IsBlocking() const { return IsBlockingError(GetError()); }
+
+ enum ConnState {
+ CS_CLOSED,
+ CS_CONNECTING,
+ CS_CONNECTED
+ };
+ virtual ConnState GetState() const = 0;
+
+ // Fills in the given uint16 with the current estimate of the MTU along the
+ // path to the address to which this socket is connected.
+ virtual int EstimateMTU(uint16* mtu) = 0;
+
+ enum Option {
+ OPT_DONTFRAGMENT,
+ OPT_RCVBUF, // receive buffer size
+ OPT_SNDBUF, // send buffer size
+ OPT_NODELAY // whether Nagle algorithm is enabled
+ };
+ virtual int GetOption(Option opt, int* value) = 0;
+ virtual int SetOption(Option opt, int value) = 0;
+
+ protected:
+ Socket() {}
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(Socket);
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKET_H__
diff --git a/talk/base/socketadapters.cc b/talk/base/socketadapters.cc
new file mode 100644
index 0000000..427ebdc
--- /dev/null
+++ b/talk/base/socketadapters.cc
@@ -0,0 +1,910 @@
+/*
+ * 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.
+ */
+
+#if defined(_MSC_VER) && _MSC_VER < 1300
+#pragma warning(disable:4786)
+#endif
+
+#include <time.h>
+#include <errno.h>
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#define SECURITY_WIN32
+#include <security.h>
+#endif
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/common.h"
+#include "talk/base/httpcommon.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+
+#ifdef WIN32
+#include "talk/base/sec_buffer.h"
+#endif // WIN32
+
+namespace talk_base {
+
+BufferedReadAdapter::BufferedReadAdapter(AsyncSocket* socket, size_t size)
+ : AsyncSocketAdapter(socket), buffer_size_(size),
+ data_len_(0), buffering_(false) {
+ buffer_ = new char[buffer_size_];
+}
+
+BufferedReadAdapter::~BufferedReadAdapter() {
+ delete [] buffer_;
+}
+
+int BufferedReadAdapter::Send(const void *pv, size_t cb) {
+ if (buffering_) {
+ // TODO: Spoof error better; Signal Writeable
+ socket_->SetError(EWOULDBLOCK);
+ return -1;
+ }
+ return AsyncSocketAdapter::Send(pv, cb);
+}
+
+int BufferedReadAdapter::Recv(void *pv, size_t cb) {
+ if (buffering_) {
+ socket_->SetError(EWOULDBLOCK);
+ return -1;
+ }
+
+ size_t read = 0;
+
+ if (data_len_) {
+ read = _min(cb, data_len_);
+ memcpy(pv, buffer_, read);
+ data_len_ -= read;
+ if (data_len_ > 0) {
+ memmove(buffer_, buffer_ + read, data_len_);
+ }
+ pv = static_cast<char *>(pv) + read;
+ cb -= read;
+ }
+
+ // FIX: If cb == 0, we won't generate another read event
+
+ int res = AsyncSocketAdapter::Recv(pv, cb);
+ if (res < 0)
+ return res;
+
+ return res + static_cast<int>(read);
+}
+
+void BufferedReadAdapter::BufferInput(bool on) {
+ buffering_ = on;
+}
+
+void BufferedReadAdapter::OnReadEvent(AsyncSocket * socket) {
+ ASSERT(socket == socket_);
+
+ if (!buffering_) {
+ AsyncSocketAdapter::OnReadEvent(socket);
+ return;
+ }
+
+ if (data_len_ >= buffer_size_) {
+ LOG(INFO) << "Input buffer overflow";
+ ASSERT(false);
+ data_len_ = 0;
+ }
+
+ int len = socket_->Recv(buffer_ + data_len_, buffer_size_ - data_len_);
+ if (len < 0) {
+ // TODO: Do something better like forwarding the error to the user.
+ LOG_ERR(INFO) << "Recv";
+ return;
+ }
+
+ data_len_ += len;
+
+ ProcessInput(buffer_, &data_len_);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+// This is a SSL v2 CLIENT_HELLO message.
+// TODO: Should this have a session id? The response doesn't have a
+// certificate, so the hello should have a session id.
+static const uint8 kSslClientHello[] = {
+ 0x80, 0x46, // msg len
+ 0x01, // CLIENT_HELLO
+ 0x03, 0x01, // SSL 3.1
+ 0x00, 0x2d, // ciphersuite len
+ 0x00, 0x00, // session id len
+ 0x00, 0x10, // challenge len
+ 0x01, 0x00, 0x80, 0x03, 0x00, 0x80, 0x07, 0x00, 0xc0, // ciphersuites
+ 0x06, 0x00, 0x40, 0x02, 0x00, 0x80, 0x04, 0x00, 0x80, //
+ 0x00, 0x00, 0x04, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x0a, //
+ 0x00, 0xfe, 0xfe, 0x00, 0x00, 0x09, 0x00, 0x00, 0x64, //
+ 0x00, 0x00, 0x62, 0x00, 0x00, 0x03, 0x00, 0x00, 0x06, //
+ 0x1f, 0x17, 0x0c, 0xa6, 0x2f, 0x00, 0x78, 0xfc, // challenge
+ 0x46, 0x55, 0x2e, 0xb1, 0x83, 0x39, 0xf1, 0xea //
+};
+
+// This is a TLSv1 SERVER_HELLO message.
+static const uint8 kSslServerHello[] = {
+ 0x16, // handshake message
+ 0x03, 0x01, // SSL 3.1
+ 0x00, 0x4a, // message len
+ 0x02, // SERVER_HELLO
+ 0x00, 0x00, 0x46, // handshake len
+ 0x03, 0x01, // SSL 3.1
+ 0x42, 0x85, 0x45, 0xa7, 0x27, 0xa9, 0x5d, 0xa0, // server random
+ 0xb3, 0xc5, 0xe7, 0x53, 0xda, 0x48, 0x2b, 0x3f, //
+ 0xc6, 0x5a, 0xca, 0x89, 0xc1, 0x58, 0x52, 0xa1, //
+ 0x78, 0x3c, 0x5b, 0x17, 0x46, 0x00, 0x85, 0x3f, //
+ 0x20, // session id len
+ 0x0e, 0xd3, 0x06, 0x72, 0x5b, 0x5b, 0x1b, 0x5f, // session id
+ 0x15, 0xac, 0x13, 0xf9, 0x88, 0x53, 0x9d, 0x9b, //
+ 0xe8, 0x3d, 0x7b, 0x0c, 0x30, 0x32, 0x6e, 0x38, //
+ 0x4d, 0xa2, 0x75, 0x57, 0x41, 0x6c, 0x34, 0x5c, //
+ 0x00, 0x04, // RSA/RC4-128/MD5
+ 0x00 // null compression
+};
+
+AsyncSSLSocket::AsyncSSLSocket(AsyncSocket* socket)
+ : BufferedReadAdapter(socket, 1024) {
+}
+
+int AsyncSSLSocket::Connect(const SocketAddress& addr) {
+ // Begin buffering before we connect, so that there isn't a race condition
+ // between potential senders and receiving the OnConnectEvent signal
+ BufferInput(true);
+ return BufferedReadAdapter::Connect(addr);
+}
+
+void AsyncSSLSocket::OnConnectEvent(AsyncSocket * socket) {
+ ASSERT(socket == socket_);
+ // TODO: we could buffer output too...
+ VERIFY(sizeof(kSslClientHello) ==
+ DirectSend(kSslClientHello, sizeof(kSslClientHello)));
+}
+
+void AsyncSSLSocket::ProcessInput(char* data, size_t* len) {
+ if (*len < sizeof(kSslServerHello))
+ return;
+
+ if (memcmp(kSslServerHello, data, sizeof(kSslServerHello)) != 0) {
+ Close();
+ SignalCloseEvent(this, 0); // TODO: error code?
+ return;
+ }
+
+ *len -= sizeof(kSslServerHello);
+ if (*len > 0) {
+ memmove(data, data + sizeof(kSslServerHello), *len);
+ }
+
+ bool remainder = (*len > 0);
+ BufferInput(false);
+ SignalConnectEvent(this);
+
+ // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+ if (remainder)
+ SignalReadEvent(this);
+}
+
+AsyncSSLServerSocket::AsyncSSLServerSocket(AsyncSocket* socket)
+ : BufferedReadAdapter(socket, 1024) {
+ BufferInput(true);
+}
+
+void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
+ // We only accept client hello messages.
+ if (*len < sizeof(kSslClientHello)) {
+ return;
+ }
+
+ if (memcmp(kSslClientHello, data, sizeof(kSslClientHello)) != 0) {
+ Close();
+ SignalCloseEvent(this, 0);
+ return;
+ }
+
+ *len -= sizeof(kSslClientHello);
+
+ // Clients should not send more data until the handshake is completed.
+ ASSERT(*len == 0);
+
+ // Send a server hello back to the client.
+ DirectSend(kSslServerHello, sizeof(kSslServerHello));
+
+ // Handshake completed for us, redirect input to our parent.
+ BufferInput(false);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
+ const std::string& user_agent,
+ const SocketAddress& proxy,
+ const std::string& username,
+ const CryptString& password)
+ : BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent),
+ user_(username), pass_(password), force_connect_(false), state_(PS_ERROR),
+ context_(0) {
+}
+
+AsyncHttpsProxySocket::~AsyncHttpsProxySocket() {
+ delete context_;
+}
+
+int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) {
+ int ret;
+ LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect("
+ << proxy_.ToString() << ")";
+ dest_ = addr;
+ state_ = PS_INIT;
+ if (ShouldIssueConnect()) {
+ BufferInput(true);
+ }
+ ret = BufferedReadAdapter::Connect(proxy_);
+ // TODO: Set state_ appropriately if Connect fails.
+ return ret;
+}
+
+SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const {
+ return dest_;
+}
+
+int AsyncHttpsProxySocket::Close() {
+ headers_.clear();
+ state_ = PS_ERROR;
+ dest_.Clear();
+ delete context_;
+ context_ = NULL;
+ return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncHttpsProxySocket::GetState() const {
+ if (state_ < PS_TUNNEL) {
+ return CS_CONNECTING;
+ } else if (state_ == PS_TUNNEL) {
+ return CS_CONNECTED;
+ } else {
+ return CS_CLOSED;
+ }
+}
+
+void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) {
+ LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
+ if (!ShouldIssueConnect()) {
+ state_ = PS_TUNNEL;
+ BufferedReadAdapter::OnConnectEvent(socket);
+ return;
+ }
+ SendRequest();
+}
+
+void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) {
+ LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")";
+ if ((state_ == PS_WAIT_CLOSE) && (err == 0)) {
+ state_ = PS_ERROR;
+ Connect(dest_);
+ } else {
+ BufferedReadAdapter::OnCloseEvent(socket, err);
+ }
+}
+
+void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) {
+ size_t start = 0;
+ for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) {
+ if (state_ == PS_SKIP_BODY) {
+ size_t consume = _min(*len - pos, content_length_);
+ pos += consume;
+ start = pos;
+ content_length_ -= consume;
+ if (content_length_ == 0) {
+ EndResponse();
+ }
+ continue;
+ }
+
+ if (data[pos++] != '\n')
+ continue;
+
+ size_t len = pos - start - 1;
+ if ((len > 0) && (data[start + len - 1] == '\r'))
+ --len;
+
+ data[start + len] = 0;
+ ProcessLine(data + start, len);
+ start = pos;
+ }
+
+ *len -= start;
+ if (*len > 0) {
+ memmove(data, data + start, *len);
+ }
+
+ if (state_ != PS_TUNNEL)
+ return;
+
+ bool remainder = (*len > 0);
+ BufferInput(false);
+ SignalConnectEvent(this);
+
+ // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+ if (remainder)
+ SignalReadEvent(this); // TODO: signal this??
+}
+
+bool AsyncHttpsProxySocket::ShouldIssueConnect() const {
+ // TODO: Think about whether a more sophisticated test
+ // than dest port == 80 is needed.
+ return force_connect_ || (dest_.port() != 80);
+}
+
+void AsyncHttpsProxySocket::SendRequest() {
+ std::stringstream ss;
+ ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n";
+ ss << "User-Agent: " << agent_ << "\r\n";
+ ss << "Host: " << dest_.IPAsString() << "\r\n";
+ ss << "Content-Length: 0\r\n";
+ ss << "Proxy-Connection: Keep-Alive\r\n";
+ ss << headers_;
+ ss << "\r\n";
+ std::string str = ss.str();
+ DirectSend(str.c_str(), str.size());
+ state_ = PS_LEADER;
+ expect_close_ = true;
+ content_length_ = 0;
+ headers_.clear();
+
+ LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str;
+}
+
+void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) {
+ LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data;
+
+ if (len == 0) {
+ if (state_ == PS_TUNNEL_HEADERS) {
+ state_ = PS_TUNNEL;
+ } else if (state_ == PS_ERROR_HEADERS) {
+ Error(defer_error_);
+ return;
+ } else if (state_ == PS_SKIP_HEADERS) {
+ if (content_length_) {
+ state_ = PS_SKIP_BODY;
+ } else {
+ EndResponse();
+ return;
+ }
+ } else {
+ static bool report = false;
+ if (!unknown_mechanisms_.empty() && !report) {
+ report = true;
+ std::string msg(
+ "Unable to connect to the Google Talk service due to an incompatibility "
+ "with your proxy.\r\nPlease help us resolve this issue by submitting the "
+ "following information to us using our technical issue submission form "
+ "at:\r\n\r\n"
+ "http://www.google.com/support/talk/bin/request.py\r\n\r\n"
+ "We apologize for the inconvenience.\r\n\r\n"
+ "Information to submit to Google: "
+ );
+ //std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: ");
+ msg.append(unknown_mechanisms_);
+#ifdef WIN32
+ MessageBoxA(0, msg.c_str(), "Oops!", MB_OK);
+#endif
+#ifdef POSIX
+ // TODO: Raise a signal so the UI can be separated.
+ LOG(LS_ERROR) << "Oops!\n\n" << msg;
+#endif
+ }
+ // Unexpected end of headers
+ Error(0);
+ return;
+ }
+ } else if (state_ == PS_LEADER) {
+ unsigned int code;
+ if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) {
+ Error(0);
+ return;
+ }
+ switch (code) {
+ case 200:
+ // connection good!
+ state_ = PS_TUNNEL_HEADERS;
+ return;
+#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407)
+#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ
+#endif
+ case 407: // HTTP_STATUS_PROXY_AUTH_REQ
+ state_ = PS_AUTHENTICATE;
+ return;
+ default:
+ defer_error_ = 0;
+ state_ = PS_ERROR_HEADERS;
+ return;
+ }
+ } else if ((state_ == PS_AUTHENTICATE)
+ && (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) {
+ std::string response, auth_method;
+ switch (HttpAuthenticate(data + 19, len - 19,
+ proxy_, "CONNECT", "/",
+ user_, pass_, context_, response, auth_method)) {
+ case HAR_IGNORE:
+ LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method;
+ if (!unknown_mechanisms_.empty())
+ unknown_mechanisms_.append(", ");
+ unknown_mechanisms_.append(auth_method);
+ break;
+ case HAR_RESPONSE:
+ headers_ = "Proxy-Authorization: ";
+ headers_.append(response);
+ headers_.append("\r\n");
+ state_ = PS_SKIP_HEADERS;
+ unknown_mechanisms_.clear();
+ break;
+ case HAR_CREDENTIALS:
+ defer_error_ = SOCKET_EACCES;
+ state_ = PS_ERROR_HEADERS;
+ unknown_mechanisms_.clear();
+ break;
+ case HAR_ERROR:
+ defer_error_ = 0;
+ state_ = PS_ERROR_HEADERS;
+ unknown_mechanisms_.clear();
+ break;
+ }
+ } else if (_strnicmp(data, "Content-Length:", 15) == 0) {
+ content_length_ = strtoul(data + 15, 0, 0);
+ } else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) {
+ expect_close_ = false;
+ /*
+ } else if (_strnicmp(data, "Connection: close", 17) == 0) {
+ expect_close_ = true;
+ */
+ }
+}
+
+void AsyncHttpsProxySocket::EndResponse() {
+ if (!expect_close_) {
+ SendRequest();
+ return;
+ }
+
+ // No point in waiting for the server to close... let's close now
+ // TODO: Refactor out PS_WAIT_CLOSE
+ state_ = PS_WAIT_CLOSE;
+ BufferedReadAdapter::Close();
+ OnCloseEvent(this, 0);
+}
+
+void AsyncHttpsProxySocket::Error(int error) {
+ BufferInput(false);
+ Close();
+ SetError(error);
+ SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
+ const SocketAddress& proxy,
+ const std::string& username,
+ const CryptString& password)
+ : BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy),
+ user_(username), pass_(password) {
+}
+
+int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
+ int ret;
+ dest_ = addr;
+ state_ = SS_INIT;
+ BufferInput(true);
+ ret = BufferedReadAdapter::Connect(proxy_);
+ // TODO: Set state_ appropriately if Connect fails.
+ return ret;
+}
+
+SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
+ return dest_;
+}
+
+int AsyncSocksProxySocket::Close() {
+ state_ = SS_ERROR;
+ dest_.Clear();
+ return BufferedReadAdapter::Close();
+}
+
+Socket::ConnState AsyncSocksProxySocket::GetState() const {
+ if (state_ < SS_TUNNEL) {
+ return CS_CONNECTING;
+ } else if (state_ == SS_TUNNEL) {
+ return CS_CONNECTED;
+ } else {
+ return CS_CLOSED;
+ }
+}
+
+void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
+ SendHello();
+}
+
+void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
+ ASSERT(state_ < SS_TUNNEL);
+
+ ByteBuffer response(data, *len);
+
+ if (state_ == SS_HELLO) {
+ uint8 ver, method;
+ if (!response.ReadUInt8(&ver) ||
+ !response.ReadUInt8(&method))
+ return;
+
+ if (ver != 5) {
+ Error(0);
+ return;
+ }
+
+ if (method == 0) {
+ SendConnect();
+ } else if (method == 2) {
+ SendAuth();
+ } else {
+ Error(0);
+ return;
+ }
+ } else if (state_ == SS_AUTH) {
+ uint8 ver, status;
+ if (!response.ReadUInt8(&ver) ||
+ !response.ReadUInt8(&status))
+ return;
+
+ if ((ver != 1) || (status != 0)) {
+ Error(SOCKET_EACCES);
+ return;
+ }
+
+ SendConnect();
+ } else if (state_ == SS_CONNECT) {
+ uint8 ver, rep, rsv, atyp;
+ if (!response.ReadUInt8(&ver) ||
+ !response.ReadUInt8(&rep) ||
+ !response.ReadUInt8(&rsv) ||
+ !response.ReadUInt8(&atyp))
+ return;
+
+ if ((ver != 5) || (rep != 0)) {
+ Error(0);
+ return;
+ }
+
+ uint16 port;
+ if (atyp == 1) {
+ uint32 addr;
+ if (!response.ReadUInt32(&addr) ||
+ !response.ReadUInt16(&port))
+ return;
+ LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+ } else if (atyp == 3) {
+ uint8 len;
+ std::string addr;
+ if (!response.ReadUInt8(&len) ||
+ !response.ReadString(&addr, len) ||
+ !response.ReadUInt16(&port))
+ return;
+ LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
+ } else if (atyp == 4) {
+ std::string addr;
+ if (!response.ReadString(&addr, 16) ||
+ !response.ReadUInt16(&port))
+ return;
+ LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
+ } else {
+ Error(0);
+ return;
+ }
+
+ state_ = SS_TUNNEL;
+ }
+
+ // Consume parsed data
+ *len = response.Length();
+ memcpy(data, response.Data(), *len);
+
+ if (state_ != SS_TUNNEL)
+ return;
+
+ bool remainder = (*len > 0);
+ BufferInput(false);
+ SignalConnectEvent(this);
+
+ // FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
+ if (remainder)
+ SignalReadEvent(this); // TODO: signal this??
+}
+
+void AsyncSocksProxySocket::SendHello() {
+ ByteBuffer request;
+ request.WriteUInt8(5); // Socks Version
+ if (user_.empty()) {
+ request.WriteUInt8(1); // Authentication Mechanisms
+ request.WriteUInt8(0); // No authentication
+ } else {
+ request.WriteUInt8(2); // Authentication Mechanisms
+ request.WriteUInt8(0); // No authentication
+ request.WriteUInt8(2); // Username/Password
+ }
+ DirectSend(request.Data(), request.Length());
+ state_ = SS_HELLO;
+}
+
+void AsyncSocksProxySocket::SendAuth() {
+ ByteBuffer request;
+ request.WriteUInt8(1); // Negotiation Version
+ request.WriteUInt8(static_cast<uint8>(user_.size()));
+ request.WriteString(user_); // Username
+ request.WriteUInt8(static_cast<uint8>(pass_.GetLength()));
+ size_t len = pass_.GetLength() + 1;
+ char * sensitive = new char[len];
+ pass_.CopyTo(sensitive, true);
+ request.WriteString(sensitive); // Password
+ memset(sensitive, 0, len);
+ delete [] sensitive;
+ DirectSend(request.Data(), request.Length());
+ state_ = SS_AUTH;
+}
+
+void AsyncSocksProxySocket::SendConnect() {
+ ByteBuffer request;
+ request.WriteUInt8(5); // Socks Version
+ request.WriteUInt8(1); // CONNECT
+ request.WriteUInt8(0); // Reserved
+ if (dest_.IsUnresolved()) {
+ std::string hostname = dest_.IPAsString();
+ request.WriteUInt8(3); // DOMAINNAME
+ request.WriteUInt8(static_cast<uint8>(hostname.size()));
+ request.WriteString(hostname); // Destination Hostname
+ } else {
+ request.WriteUInt8(1); // IPV4
+ request.WriteUInt32(dest_.ip()); // Destination IP
+ }
+ request.WriteUInt16(dest_.port()); // Destination Port
+ DirectSend(request.Data(), request.Length());
+ state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxySocket::Error(int error) {
+ state_ = SS_ERROR;
+ BufferInput(false);
+ Close();
+ SetError(SOCKET_EACCES);
+ SignalCloseEvent(this, error);
+}
+
+AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket)
+ : AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) {
+ BufferInput(true);
+}
+
+void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
+ // TODO: See if the whole message has arrived
+ ASSERT(state_ < SS_CONNECT_PENDING);
+
+ ByteBuffer response(data, *len);
+ if (state_ == SS_HELLO) {
+ HandleHello(&response);
+ } else if (state_ == SS_AUTH) {
+ HandleAuth(&response);
+ } else if (state_ == SS_CONNECT) {
+ HandleConnect(&response);
+ }
+
+ // Consume parsed data
+ *len = response.Length();
+ memcpy(data, response.Data(), *len);
+}
+
+void AsyncSocksProxyServerSocket::DirectSend(const ByteBuffer& buf) {
+ BufferedReadAdapter::DirectSend(buf.Data(), buf.Length());
+}
+
+void AsyncSocksProxyServerSocket::HandleHello(ByteBuffer* request) {
+ uint8 ver, num_methods;
+ if (!request->ReadUInt8(&ver) ||
+ !request->ReadUInt8(&num_methods)) {
+ Error(0);
+ return;
+ }
+
+ if (ver != 5) {
+ Error(0);
+ return;
+ }
+
+ // Handle either no-auth (0) or user/pass auth (2)
+ uint8 method = 0xFF;
+ if (num_methods > 0 && !request->ReadUInt8(&method)) {
+ Error(0);
+ return;
+ }
+
+ // TODO: Ask the server which method to use.
+ SendHelloReply(method);
+ if (method == 0) {
+ state_ = SS_CONNECT;
+ } else if (method == 2) {
+ state_ = SS_AUTH;
+ } else {
+ state_ = SS_ERROR;
+ }
+}
+
+void AsyncSocksProxyServerSocket::SendHelloReply(int method) {
+ ByteBuffer response;
+ response.WriteUInt8(5); // Socks Version
+ response.WriteUInt8(method); // Auth method
+ DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleAuth(ByteBuffer* request) {
+ uint8 ver, user_len, pass_len;
+ std::string user, pass;
+ if (!request->ReadUInt8(&ver) ||
+ !request->ReadUInt8(&user_len) ||
+ !request->ReadString(&user, user_len) ||
+ !request->ReadUInt8(&pass_len) ||
+ !request->ReadString(&pass, pass_len)) {
+ Error(0);
+ return;
+ }
+
+ // TODO: Allow for checking of credentials.
+ SendAuthReply(0);
+ state_ = SS_CONNECT;
+}
+
+void AsyncSocksProxyServerSocket::SendAuthReply(int result) {
+ ByteBuffer response;
+ response.WriteUInt8(1); // Negotiation Version
+ response.WriteUInt8(result);
+ DirectSend(response);
+}
+
+void AsyncSocksProxyServerSocket::HandleConnect(ByteBuffer* request) {
+ uint8 ver, command, reserved, addr_type;
+ uint32 ip;
+ uint16 port;
+ if (!request->ReadUInt8(&ver) ||
+ !request->ReadUInt8(&command) ||
+ !request->ReadUInt8(&reserved) ||
+ !request->ReadUInt8(&addr_type) ||
+ !request->ReadUInt32(&ip) ||
+ !request->ReadUInt16(&port)) {
+ Error(0);
+ return;
+ }
+
+ if (ver != 5 || command != 1 ||
+ reserved != 0 || addr_type != 1) {
+ Error(0);
+ return;
+ }
+
+ SignalConnectRequest(this, SocketAddress(ip, port));
+ state_ = SS_CONNECT_PENDING;
+}
+
+void AsyncSocksProxyServerSocket::SendConnectResult(int result,
+ const SocketAddress& addr) {
+ if (state_ != SS_CONNECT_PENDING)
+ return;
+
+ ByteBuffer response;
+ response.WriteUInt8(5); // Socks version
+ response.WriteUInt8((result != 0)); // 0x01 is generic error
+ response.WriteUInt8(0); // reserved
+ response.WriteUInt8(1); // IPv4 address
+ response.WriteUInt32(addr.ip());
+ response.WriteUInt16(addr.port());
+ DirectSend(response);
+ BufferInput(false);
+ state_ = SS_TUNNEL;
+}
+
+void AsyncSocksProxyServerSocket::Error(int error) {
+ state_ = SS_ERROR;
+ BufferInput(false);
+ Close();
+ SetError(SOCKET_EACCES);
+ SignalCloseEvent(this, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingSocketAdapter::LoggingSocketAdapter(AsyncSocket* socket,
+ LoggingSeverity level,
+ const char * label, bool hex_mode)
+ : AsyncSocketAdapter(socket), level_(level), hex_mode_(hex_mode) {
+ label_.append("[");
+ label_.append(label);
+ label_.append("]");
+}
+
+int LoggingSocketAdapter::Send(const void *pv, size_t cb) {
+ int res = AsyncSocketAdapter::Send(pv, cb);
+ if (res > 0)
+ LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+ return res;
+}
+
+int LoggingSocketAdapter::SendTo(const void *pv, size_t cb,
+ const SocketAddress& addr) {
+ int res = AsyncSocketAdapter::SendTo(pv, cb, addr);
+ if (res > 0)
+ LogMultiline(level_, label_.c_str(), false, pv, res, hex_mode_, &lms_);
+ return res;
+}
+
+int LoggingSocketAdapter::Recv(void *pv, size_t cb) {
+ int res = AsyncSocketAdapter::Recv(pv, cb);
+ if (res > 0)
+ LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+ return res;
+}
+
+int LoggingSocketAdapter::RecvFrom(void *pv, size_t cb, SocketAddress *paddr) {
+ int res = AsyncSocketAdapter::RecvFrom(pv, cb, paddr);
+ if (res > 0)
+ LogMultiline(level_, label_.c_str(), true, pv, res, hex_mode_, &lms_);
+ return res;
+}
+
+int LoggingSocketAdapter::Close() {
+ LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+ LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+ LOG_V(level_) << label_ << " Closed locally";
+ return socket_->Close();
+}
+
+void LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) {
+ LOG_V(level_) << label_ << " Connected";
+ AsyncSocketAdapter::OnConnectEvent(socket);
+}
+
+void LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) {
+ LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+ LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+ LOG_V(level_) << label_ << " Closed with error: " << err;
+ AsyncSocketAdapter::OnCloseEvent(socket, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/socketadapters.h b/talk/base/socketadapters.h
new file mode 100644
index 0000000..320da6f
--- /dev/null
+++ b/talk/base/socketadapters.h
@@ -0,0 +1,261 @@
+/*
+ * 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_SOCKETADAPTERS_H_
+#define TALK_BASE_SOCKETADAPTERS_H_
+
+#include <map>
+#include <string>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/cryptstring.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+struct HttpAuthContext;
+class ByteBuffer;
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that can buffer and process data internally,
+// as in the case of connecting to a proxy, where you must speak the proxy
+// protocol before commencing normal socket behavior.
+class BufferedReadAdapter : public AsyncSocketAdapter {
+ public:
+ BufferedReadAdapter(AsyncSocket* socket, size_t buffer_size);
+ virtual ~BufferedReadAdapter();
+
+ virtual int Send(const void* pv, size_t cb);
+ virtual int Recv(void* pv, size_t cb);
+
+ protected:
+ int DirectSend(const void* pv, size_t cb) {
+ return AsyncSocketAdapter::Send(pv, cb);
+ }
+
+ void BufferInput(bool on = true);
+ virtual void ProcessInput(char* data, size_t* len) = 0;
+
+ virtual void OnReadEvent(AsyncSocket * socket);
+
+ private:
+ char * buffer_;
+ size_t buffer_size_, data_len_;
+ bool buffering_;
+ DISALLOW_EVIL_CONSTRUCTORS(BufferedReadAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Interface for implementing proxy server sockets.
+class AsyncProxyServerSocket : public BufferedReadAdapter {
+ public:
+ AsyncProxyServerSocket(AsyncSocket* socket, size_t buffer_size)
+ : BufferedReadAdapter(socket, buffer_size) {}
+ sigslot::signal2<AsyncProxyServerSocket*,
+ const SocketAddress&> SignalConnectRequest;
+ virtual void SendConnectResult(int err, const SocketAddress& addr) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that performs the client side of a
+// fake SSL handshake. Used for "ssltcp" P2P functionality.
+class AsyncSSLSocket : public BufferedReadAdapter {
+ public:
+ explicit AsyncSSLSocket(AsyncSocket* socket);
+
+ virtual int Connect(const SocketAddress& addr);
+
+ protected:
+ virtual void OnConnectEvent(AsyncSocket* socket);
+ virtual void ProcessInput(char* data, size_t* len);
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLSocket);
+};
+
+// Implements a socket adapter that performs the server side of a
+// fake SSL handshake. Used when implementing a relay server that does "ssltcp".
+class AsyncSSLServerSocket : public BufferedReadAdapter {
+ public:
+ explicit AsyncSSLServerSocket(AsyncSocket* socket);
+
+ protected:
+ virtual void ProcessInput(char* data, size_t* len);
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncSSLServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the HTTP/S proxy protocol.
+class AsyncHttpsProxySocket : public BufferedReadAdapter {
+ public:
+ AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent,
+ const SocketAddress& proxy,
+ const std::string& username, const CryptString& password);
+ virtual ~AsyncHttpsProxySocket();
+
+ // If connect is forced, the adapter will always issue an HTTP CONNECT to the
+ // target address. Otherwise, it will connect only if the destination port
+ // is not port 80.
+ void SetForceConnect(bool force) { force_connect_ = force; }
+
+ virtual int Connect(const SocketAddress& addr);
+ virtual SocketAddress GetRemoteAddress() const;
+ virtual int Close();
+ virtual ConnState GetState() const;
+
+ protected:
+ virtual void OnConnectEvent(AsyncSocket* socket);
+ virtual void OnCloseEvent(AsyncSocket* socket, int err);
+ virtual void ProcessInput(char* data, size_t* len);
+
+ bool ShouldIssueConnect() const;
+ void SendRequest();
+ void ProcessLine(char* data, size_t len);
+ void EndResponse();
+ void Error(int error);
+
+ private:
+ SocketAddress proxy_, dest_;
+ std::string agent_, user_, headers_;
+ CryptString pass_;
+ bool force_connect_;
+ size_t content_length_;
+ int defer_error_;
+ bool expect_close_;
+ enum ProxyState {
+ PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS,
+ PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR
+ } state_;
+ HttpAuthContext * context_;
+ std::string unknown_mechanisms_;
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxySocket);
+};
+
+/* TODO: Implement this.
+class AsyncHttpsProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+ explicit AsyncHttpsProxyServerSocket(AsyncSocket* socket);
+
+ private:
+ virtual void ProcessInput(char * data, size_t& len);
+ void Error(int error);
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncHttpsProxyServerSocket);
+};
+*/
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that speaks the SOCKS proxy protocol.
+class AsyncSocksProxySocket : public BufferedReadAdapter {
+ public:
+ AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy,
+ const std::string& username, const CryptString& password);
+
+ virtual int Connect(const SocketAddress& addr);
+ virtual SocketAddress GetRemoteAddress() const;
+ virtual int Close();
+ virtual ConnState GetState() const;
+
+ protected:
+ virtual void OnConnectEvent(AsyncSocket* socket);
+ virtual void ProcessInput(char* data, size_t* len);
+
+ void SendHello();
+ void SendConnect();
+ void SendAuth();
+ void Error(int error);
+
+ private:
+ enum State {
+ SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR
+ };
+ State state_;
+ SocketAddress proxy_, dest_;
+ std::string user_;
+ CryptString pass_;
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxySocket);
+};
+
+// Implements a proxy server socket for the SOCKS protocol.
+class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket {
+ public:
+ explicit AsyncSocksProxyServerSocket(AsyncSocket* socket);
+
+ private:
+ virtual void ProcessInput(char* data, size_t* len);
+ void DirectSend(const ByteBuffer& buf);
+
+ void HandleHello(ByteBuffer* request);
+ void SendHelloReply(int method);
+ void HandleAuth(ByteBuffer* request);
+ void SendAuthReply(int result);
+ void HandleConnect(ByteBuffer* request);
+ virtual void SendConnectResult(int result, const SocketAddress& addr);
+
+ void Error(int error);
+
+ static const int kBufferSize = 1024;
+ enum State {
+ SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR
+ };
+ State state_;
+ DISALLOW_EVIL_CONSTRUCTORS(AsyncSocksProxyServerSocket);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Implements a socket adapter that logs everything that it sends and receives.
+class LoggingSocketAdapter : public AsyncSocketAdapter {
+ public:
+ LoggingSocketAdapter(AsyncSocket* socket, LoggingSeverity level,
+ const char * label, bool hex_mode = false);
+
+ virtual int Send(const void *pv, size_t cb);
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+ virtual int Recv(void *pv, size_t cb);
+ virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr);
+ virtual int Close();
+
+ protected:
+ virtual void OnConnectEvent(AsyncSocket * socket);
+ virtual void OnCloseEvent(AsyncSocket * socket, int err);
+
+ private:
+ LoggingSeverity level_;
+ std::string label_;
+ bool hex_mode_;
+ LogMultilineState lms_;
+ DISALLOW_EVIL_CONSTRUCTORS(LoggingSocketAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETADAPTERS_H_
diff --git a/talk/base/socketaddress.cc b/talk/base/socketaddress.cc
new file mode 100644
index 0000000..cbb0805
--- /dev/null
+++ b/talk/base/socketaddress.cc
@@ -0,0 +1,358 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <unistd.h>
+#endif
+
+#include <sstream>
+
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/socketaddress.h"
+
+#ifdef WIN32
+// Win32 doesn't provide inet_aton, so we add our own version here.
+// Since inet_addr returns 0xFFFFFFFF on error, if we get this value
+// we need to test the input to see if the address really was 255.255.255.255.
+// This is slightly fragile, but better than doing nothing.
+int inet_aton(const char* cp, struct in_addr* inp) {
+ inp->s_addr = inet_addr(cp);
+ return (inp->s_addr == INADDR_NONE &&
+ strcmp(cp, "255.255.255.255") != 0) ? 0 : 1;
+}
+#endif // WIN32
+
+namespace talk_base {
+
+SocketAddress::SocketAddress() {
+ Clear();
+}
+
+SocketAddress::SocketAddress(const std::string& hostname, int port) {
+ SetIP(hostname);
+ SetPort(port);
+}
+
+SocketAddress::SocketAddress(uint32 ip, int port) {
+ SetIP(ip);
+ SetPort(port);
+}
+
+SocketAddress::SocketAddress(const SocketAddress& addr) {
+ this->operator=(addr);
+}
+
+void SocketAddress::Clear() {
+ hostname_.clear();
+ ip_ = 0;
+ port_ = 0;
+}
+
+bool SocketAddress::IsNil() const {
+ return hostname_.empty() && (0 == ip_) && (0 == port_);
+}
+
+bool SocketAddress::IsComplete() const {
+ return (0 != ip_) && (0 != port_);
+}
+
+SocketAddress& SocketAddress::operator=(const SocketAddress& addr) {
+ hostname_ = addr.hostname_;
+ ip_ = addr.ip_;
+ port_ = addr.port_;
+ return *this;
+}
+
+void SocketAddress::SetIP(uint32 ip) {
+ hostname_.clear();
+ ip_ = ip;
+}
+
+void SocketAddress::SetIP(const std::string& hostname) {
+ hostname_ = hostname;
+ ip_ = StringToIP(hostname);
+}
+
+void SocketAddress::SetResolvedIP(uint32 ip) {
+ ip_ = ip;
+}
+
+void SocketAddress::SetPort(int port) {
+ ASSERT((0 <= port) && (port < 65536));
+ port_ = port;
+}
+
+uint32 SocketAddress::ip() const {
+ return ip_;
+}
+
+uint16 SocketAddress::port() const {
+ return port_;
+}
+
+std::string SocketAddress::IPAsString() const {
+ if (!hostname_.empty())
+ return hostname_;
+ return IPToString(ip_);
+}
+
+std::string SocketAddress::PortAsString() const {
+ std::ostringstream ost;
+ ost << port_;
+ return ost.str();
+}
+
+std::string SocketAddress::ToString() const {
+ std::ostringstream ost;
+ ost << IPAsString();
+ ost << ":";
+ ost << port();
+ return ost.str();
+}
+
+bool SocketAddress::FromString(const std::string& str) {
+ std::string::size_type pos = str.find(':');
+ if (std::string::npos == pos)
+ return false;
+ SetPort(strtoul(str.substr(pos + 1).c_str(), NULL, 10));
+ SetIP(str.substr(0, pos));
+ return true;
+}
+
+std::ostream& operator<<(std::ostream& os, const SocketAddress& addr) {
+ os << addr.IPAsString() << ":" << addr.port();
+ return os;
+}
+
+bool SocketAddress::IsAnyIP() const {
+ return (ip_ == 0);
+}
+
+bool SocketAddress::IsLoopbackIP() const {
+ if (0 == ip_) {
+ return (0 == stricmp(hostname_.c_str(), "localhost"));
+ } else {
+ return ((ip_ >> 24) == 127);
+ }
+}
+
+bool SocketAddress::IsLocalIP() const {
+ if (IsLoopbackIP())
+ return true;
+
+ std::vector<uint32> ips;
+ if (0 == ip_) {
+ if (!hostname_.empty()
+ && (0 == stricmp(hostname_.c_str(), GetHostname().c_str()))) {
+ return true;
+ }
+ } else if (GetLocalIPs(ips)) {
+ for (size_t i = 0; i < ips.size(); ++i) {
+ if (ips[i] == ip_) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool SocketAddress::IsPrivateIP() const {
+ return ((ip_ >> 24) == 127) ||
+ ((ip_ >> 24) == 10) ||
+ ((ip_ >> 20) == ((172 << 4) | 1)) ||
+ ((ip_ >> 16) == ((192 << 8) | 168)) ||
+ ((ip_ >> 16) == ((169 << 8) | 254));
+}
+
+bool SocketAddress::IsUnresolvedIP() const {
+ return IsAny() && !hostname_.empty();
+}
+
+bool SocketAddress::ResolveIP(bool force, int* error) {
+ if (hostname_.empty()) {
+ // nothing to resolve
+ } else if (!force && !IsAny()) {
+ // already resolved
+ } else {
+ LOG_F(LS_VERBOSE) << "(" << hostname_ << ")";
+ int errcode = 0;
+ if (hostent* pHost = SafeGetHostByName(hostname_.c_str(), &errcode)) {
+ ip_ = NetworkToHost32(*reinterpret_cast<uint32*>(pHost->h_addr_list[0]));
+ LOG_F(LS_VERBOSE) << "(" << hostname_ << ") resolved to: "
+ << IPToString(ip_);
+ FreeHostEnt(pHost);
+ } else {
+ LOG_F(LS_ERROR) << "(" << hostname_ << ") err: " << errcode;
+ }
+ if (error) {
+ *error = errcode;
+ }
+ }
+ return (ip_ != 0);
+}
+
+bool SocketAddress::operator==(const SocketAddress& addr) const {
+ return EqualIPs(addr) && EqualPorts(addr);
+}
+
+bool SocketAddress::operator<(const SocketAddress& addr) const {
+ if (ip_ < addr.ip_)
+ return true;
+ else if (addr.ip_ < ip_)
+ return false;
+
+ // We only check hostnames if both IPs are zero. This matches EqualIPs()
+ if (addr.ip_ == 0) {
+ if (hostname_ < addr.hostname_)
+ return true;
+ else if (addr.hostname_ < hostname_)
+ return false;
+ }
+
+ return port_ < addr.port_;
+}
+
+bool SocketAddress::EqualIPs(const SocketAddress& addr) const {
+ return (ip_ == addr.ip_) && ((ip_ != 0) || (hostname_ == addr.hostname_));
+}
+
+bool SocketAddress::EqualPorts(const SocketAddress& addr) const {
+ return (port_ == addr.port_);
+}
+
+size_t SocketAddress::Hash() const {
+ size_t h = 0;
+ h ^= ip_;
+ h ^= port_ | (port_ << 16);
+ return h;
+}
+
+size_t SocketAddress::Size_() const {
+ return sizeof(ip_) + sizeof(port_) + 2;
+}
+
+bool SocketAddress::Write_(char* buf, int len) const {
+ if (len < static_cast<int>(Size_()))
+ return false;
+ buf[0] = 0;
+ buf[1] = AF_INET;
+ SetBE16(buf + 2, port_);
+ SetBE32(buf + 4, ip_);
+ return true;
+}
+
+bool SocketAddress::Read_(const char* buf, int len) {
+ if (len < static_cast<int>(Size_()) || buf[1] != AF_INET)
+ return false;
+ port_ = GetBE16(buf + 2);
+ ip_ = GetBE32(buf + 4);
+ return true;
+}
+
+void SocketAddress::ToSockAddr(sockaddr_in* saddr) const {
+ memset(saddr, 0, sizeof(*saddr));
+ saddr->sin_family = AF_INET;
+ saddr->sin_port = HostToNetwork16(port_);
+ if (0 == ip_) {
+ saddr->sin_addr.s_addr = INADDR_ANY;
+ } else {
+ saddr->sin_addr.s_addr = HostToNetwork32(ip_);
+ }
+}
+
+bool SocketAddress::FromSockAddr(const sockaddr_in& saddr) {
+ if (saddr.sin_family != AF_INET)
+ return false;
+ SetIP(NetworkToHost32(saddr.sin_addr.s_addr));
+ SetPort(NetworkToHost16(saddr.sin_port));
+ return true;
+}
+
+std::string SocketAddress::IPToString(uint32 ip) {
+ std::ostringstream ost;
+ ost << ((ip >> 24) & 0xff);
+ ost << '.';
+ ost << ((ip >> 16) & 0xff);
+ ost << '.';
+ ost << ((ip >> 8) & 0xff);
+ ost << '.';
+ ost << ((ip >> 0) & 0xff);
+ return ost.str();
+}
+
+bool SocketAddress::StringToIP(const std::string& hostname, uint32* ip) {
+ in_addr addr;
+ if (inet_aton(hostname.c_str(), &addr) == 0)
+ return false;
+ *ip = NetworkToHost32(addr.s_addr);
+ return true;
+}
+
+uint32 SocketAddress::StringToIP(const std::string& hostname) {
+ uint32 ip = 0;
+ StringToIP(hostname, &ip);
+ return ip;
+}
+
+std::string SocketAddress::GetHostname() {
+ char hostname[256];
+ if (gethostname(hostname, ARRAY_SIZE(hostname)) == 0)
+ return hostname;
+ return "";
+}
+
+bool SocketAddress::GetLocalIPs(std::vector<uint32>& ips) {
+ ips.clear();
+
+ const std::string hostname = GetHostname();
+ if (hostname.empty())
+ return false;
+
+ int errcode;
+ if (hostent* pHost = SafeGetHostByName(hostname.c_str(), &errcode)) {
+ for (size_t i = 0; pHost->h_addr_list[i]; ++i) {
+ uint32 ip =
+ NetworkToHost32(*reinterpret_cast<uint32 *>(pHost->h_addr_list[i]));
+ ips.push_back(ip);
+ }
+ FreeHostEnt(pHost);
+ return !ips.empty();
+ }
+ LOG(LS_ERROR) << "gethostbyname err: " << errcode;
+ return false;
+}
+
+} // namespace talk_base
diff --git a/talk/base/socketaddress.h b/talk/base/socketaddress.h
new file mode 100644
index 0000000..4af80c9
--- /dev/null
+++ b/talk/base/socketaddress.h
@@ -0,0 +1,191 @@
+/*
+ * 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_SOCKETADDRESS_H_
+#define TALK_BASE_SOCKETADDRESS_H_
+
+#include <string>
+#include <vector>
+#include <iosfwd>
+#include "talk/base/basictypes.h"
+#undef SetPort
+
+struct sockaddr_in;
+
+namespace talk_base {
+
+// Records an IP address and port, which are 32 and 16 bit integers,
+// respectively, both in <b>host byte-order</b>.
+class SocketAddress {
+ public:
+ // Creates a nil address.
+ SocketAddress();
+
+ // Creates the address with the given host and port. If use_dns is true,
+ // the hostname will be immediately resolved to an IP (which may block for
+ // several seconds if DNS is not available). Alternately, set use_dns to
+ // false, and then call Resolve() to complete resolution later, or use
+ // SetResolvedIP to set the IP explictly.
+ SocketAddress(const std::string& hostname, int port);
+
+ // Creates the address with the given IP and port.
+ SocketAddress(uint32 ip, int port);
+
+ // Creates a copy of the given address.
+ SocketAddress(const SocketAddress& addr);
+
+ // Resets to the nil address.
+ void Clear();
+
+ // Determines if this is a nil address (empty hostname, any IP, null port)
+ bool IsNil() const;
+
+ // Returns true if ip and port are set.
+ bool IsComplete() const;
+
+ // Replaces our address with the given one.
+ SocketAddress& operator=(const SocketAddress& addr);
+
+ // Changes the IP of this address to the given one, and clears the hostname.
+ void SetIP(uint32 ip);
+
+ // Changes the hostname of this address to the given one.
+ // Does not resolve the address; use Resolve to do so.
+ void SetIP(const std::string& hostname);
+
+ // Sets the IP address while retaining the hostname. Useful for bypassing
+ // DNS for a pre-resolved IP.
+ void SetResolvedIP(uint32 ip);
+
+ // Changes the port of this address to the given one.
+ void SetPort(int port);
+
+ // Returns the hostname
+ const std::string& hostname() const { return hostname_; }
+
+ // Returns the IP address.
+ uint32 ip() const;
+
+ // Returns the port part of this address.
+ uint16 port() const;
+
+ // Returns the IP address in dotted form.
+ std::string IPAsString() const;
+
+ // Returns the port as a string
+ std::string PortAsString() const;
+
+ // Returns hostname:port
+ std::string ToString() const;
+
+ // Parses hostname:port
+ bool FromString(const std::string& str);
+
+ friend std::ostream& operator<<(std::ostream& os, const SocketAddress& addr);
+
+ // Determines whether this represents a missing / any IP address. Hostname
+ // and/or port may be set.
+ bool IsAnyIP() const;
+ inline bool IsAny() const { return IsAnyIP(); } // deprecated
+
+ // Determines whether the IP address refers to a loopback address, i.e. within
+ // the range 127.0.0.0/8.
+ bool IsLoopbackIP() const;
+
+ // Determines wither the IP address refers to any adapter on the local
+ // machine, including the loopback adapter.
+ bool IsLocalIP() const;
+
+ // Determines whether the IP address is in one of the private ranges:
+ // 127.0.0.0/8 10.0.0.0/8 192.168.0.0/16 172.16.0.0/12.
+ bool IsPrivateIP() const;
+
+ // Determines whether the hostname has been resolved to an IP.
+ bool IsUnresolvedIP() const;
+ inline bool IsUnresolved() const { return IsUnresolvedIP(); } // deprecated
+
+ // Attempt to resolve a hostname to IP address.
+ // Returns false if resolution is required but failed, and sets error.
+ // 'force' will cause re-resolution of hostname.
+ bool ResolveIP(bool force = false, int* error = NULL);
+
+ // Determines whether this address is identical to the given one.
+ bool operator ==(const SocketAddress& addr) const;
+ inline bool operator !=(const SocketAddress& addr) const {
+ return !this->operator ==(addr);
+ }
+
+ // Compares based on IP and then port.
+ bool operator <(const SocketAddress& addr) const;
+
+ // Determines whether this address has the same IP as the one given.
+ bool EqualIPs(const SocketAddress& addr) const;
+
+ // Determines whether this address has the same port as the one given.
+ bool EqualPorts(const SocketAddress& addr) const;
+
+ // Hashes this address into a small number.
+ size_t Hash() const;
+
+ // Returns the size of this address when written.
+ size_t Size_() const;
+
+ // Writes this address into the given buffer, according to RFC 3489.
+ bool Write_(char* buf, int len) const;
+
+ // Reads this address from the given buffer, according to RFC 3489.
+ bool Read_(const char* buf, int len);
+
+ // Write this address to a sockaddr_in.
+ void ToSockAddr(sockaddr_in* saddr) const;
+
+ // Read this address from a sockaddr_in.
+ bool FromSockAddr(const sockaddr_in& saddr);
+
+ // Converts the IP address given in compact form into dotted form.
+ static std::string IPToString(uint32 ip);
+
+ // Converts the IP address given in dotted form into compact form.
+ // Only dotted names (A.B.C.D) are resolved.
+ static bool StringToIP(const std::string& str, uint32* ip);
+ static uint32 StringToIP(const std::string& str); // deprecated
+
+ // Get local machine's hostname
+ static std::string GetHostname();
+
+ // Get a list of the local machine's ip addresses
+ static bool GetLocalIPs(std::vector<uint32>& ips);
+
+ private:
+ std::string hostname_;
+ uint32 ip_;
+ uint16 port_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETADDRESS_H_
diff --git a/talk/base/socketaddresspair.cc b/talk/base/socketaddresspair.cc
new file mode 100644
index 0000000..7f190a9
--- /dev/null
+++ b/talk/base/socketaddresspair.cc
@@ -0,0 +1,58 @@
+/*
+ * 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 "talk/base/socketaddresspair.h"
+
+namespace talk_base {
+
+SocketAddressPair::SocketAddressPair(
+ const SocketAddress& src, const SocketAddress& dest)
+ : src_(src), dest_(dest) {
+}
+
+
+bool SocketAddressPair::operator ==(const SocketAddressPair& p) const {
+ return (src_ == p.src_) && (dest_ == p.dest_);
+}
+
+bool SocketAddressPair::operator <(const SocketAddressPair& p) const {
+ if (src_ < p.src_)
+ return true;
+ if (p.src_ < src_)
+ return false;
+ if (dest_ < p.dest_)
+ return true;
+ if (p.dest_ < dest_)
+ return false;
+ return false;
+}
+
+size_t SocketAddressPair::Hash() const {
+ return src_.Hash() ^ dest_.Hash();
+}
+
+} // namespace talk_base
diff --git a/talk/base/socketaddresspair.h b/talk/base/socketaddresspair.h
new file mode 100644
index 0000000..10f5d30
--- /dev/null
+++ b/talk/base/socketaddresspair.h
@@ -0,0 +1,58 @@
+/*
+ * 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_SOCKETADDRESSPAIR_H__
+#define TALK_BASE_SOCKETADDRESSPAIR_H__
+
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+// Records a pair (source,destination) of socket addresses. The two addresses
+// identify a connection between two machines. (For UDP, this "connection" is
+// not maintained explicitly in a socket.)
+class SocketAddressPair {
+public:
+ SocketAddressPair() {}
+ SocketAddressPair(const SocketAddress& srs, const SocketAddress& dest);
+
+ const SocketAddress& source() const { return src_; }
+ const SocketAddress& destination() const { return dest_; }
+
+ bool operator ==(const SocketAddressPair& r) const;
+ bool operator <(const SocketAddressPair& r) const;
+
+ size_t Hash() const;
+
+private:
+ SocketAddress src_;
+ SocketAddress dest_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETADDRESSPAIR_H__
diff --git a/talk/base/socketfactory.h b/talk/base/socketfactory.h
new file mode 100644
index 0000000..2a4aee2
--- /dev/null
+++ b/talk/base/socketfactory.h
@@ -0,0 +1,51 @@
+/*
+ * 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_SOCKETFACTORY_H__
+#define TALK_BASE_SOCKETFACTORY_H__
+
+#include "talk/base/socket.h"
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+class SocketFactory {
+public:
+ virtual ~SocketFactory() {}
+
+ // Returns a new socket for blocking communication. The type can be
+ // SOCK_DGRAM and SOCK_STREAM.
+ virtual Socket* CreateSocket(int type) = 0;
+
+ // Returns a new socket for nonblocking communication. The type can be
+ // SOCK_DGRAM and SOCK_STREAM.
+ virtual AsyncSocket* CreateAsyncSocket(int type) = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETFACTORY_H__
diff --git a/talk/base/socketpool.cc b/talk/base/socketpool.cc
new file mode 100644
index 0000000..a5e3bcc
--- /dev/null
+++ b/talk/base/socketpool.cc
@@ -0,0 +1,292 @@
+/*
+ * 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 <iomanip>
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socketpool.h"
+#include "talk/base/socketstream.h"
+#include "talk/base/thread.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation to a separate
+// StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+StreamCache::StreamCache(StreamPool* pool) : pool_(pool) {
+}
+
+StreamCache::~StreamCache() {
+ for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+ ++it) {
+ delete it->second;
+ }
+ for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+ ++it) {
+ delete it->second;
+ }
+}
+
+StreamInterface* StreamCache::RequestConnectedStream(
+ const SocketAddress& remote, int* err) {
+ LOG_F(LS_VERBOSE) << "(" << remote << ")";
+ for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+ ++it) {
+ if (remote == it->first) {
+ it->second->SignalEvent.disconnect(this);
+ // Move from cached_ to active_
+ active_.push_front(*it);
+ cached_.erase(it);
+ if (err)
+ *err = 0;
+ LOG_F(LS_VERBOSE) << "Providing cached stream";
+ return active_.front().second;
+ }
+ }
+ if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+ // We track active streams so that we can remember their address
+ active_.push_front(ConnectedStream(remote, stream));
+ LOG_F(LS_VERBOSE) << "Providing new stream";
+ return active_.front().second;
+ }
+ return NULL;
+}
+
+void StreamCache::ReturnConnectedStream(StreamInterface* stream) {
+ for (ConnectedList::iterator it = active_.begin(); it != active_.end();
+ ++it) {
+ if (stream == it->second) {
+ LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+ if (stream->GetState() == SS_CLOSED) {
+ // Return closed streams
+ LOG_F(LS_VERBOSE) << "Returning closed stream";
+ pool_->ReturnConnectedStream(it->second);
+ } else {
+ // Monitor open streams
+ stream->SignalEvent.connect(this, &StreamCache::OnStreamEvent);
+ LOG_F(LS_VERBOSE) << "Caching stream";
+ cached_.push_front(*it);
+ }
+ active_.erase(it);
+ return;
+ }
+ }
+ ASSERT(false);
+}
+
+void StreamCache::OnStreamEvent(StreamInterface* stream, int events, int err) {
+ if ((events & SE_CLOSE) == 0) {
+ LOG_F(LS_WARNING) << "(" << events << ", " << err
+ << ") received non-close event";
+ return;
+ }
+ for (ConnectedList::iterator it = cached_.begin(); it != cached_.end();
+ ++it) {
+ if (stream == it->second) {
+ LOG_F(LS_VERBOSE) << "(" << it->first << ")";
+ // We don't cache closed streams, so return it.
+ it->second->SignalEvent.disconnect(this);
+ LOG_F(LS_VERBOSE) << "Returning closed stream";
+ pool_->ReturnConnectedStream(it->second);
+ cached_.erase(it);
+ return;
+ }
+ }
+ ASSERT(false);
+}
+
+//////////////////////////////////////////////////////////////////////
+// NewSocketPool
+//////////////////////////////////////////////////////////////////////
+
+NewSocketPool::NewSocketPool(SocketFactory* factory) : factory_(factory) {
+}
+
+NewSocketPool::~NewSocketPool() {
+}
+
+StreamInterface*
+NewSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+ AsyncSocket* socket = factory_->CreateAsyncSocket(SOCK_STREAM);
+ if (!socket) {
+ ASSERT(false);
+ if (err)
+ *err = -1;
+ return NULL;
+ }
+ if ((socket->Connect(remote) != 0) && !socket->IsBlocking()) {
+ if (err)
+ *err = socket->GetError();
+ delete socket;
+ return NULL;
+ }
+ if (err)
+ *err = 0;
+ return new SocketStream(socket);
+}
+
+void
+NewSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+ Thread::Current()->Dispose(stream);
+}
+
+//////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+//////////////////////////////////////////////////////////////////////
+
+ReuseSocketPool::ReuseSocketPool(SocketFactory* factory)
+: factory_(factory), stream_(NULL), checked_out_(false) {
+}
+
+ReuseSocketPool::~ReuseSocketPool() {
+ ASSERT(!checked_out_);
+ delete stream_;
+}
+
+StreamInterface*
+ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) {
+ // Only one socket can be used from this "pool" at a time
+ ASSERT(!checked_out_);
+ if (!stream_) {
+ LOG_F(LS_VERBOSE) << "Creating new socket";
+ AsyncSocket* socket = factory_->CreateAsyncSocket(SOCK_STREAM);
+ if (!socket) {
+ ASSERT(false);
+ if (err)
+ *err = -1;
+ return NULL;
+ }
+ stream_ = new SocketStream(socket);
+ }
+ if ((stream_->GetState() == SS_OPEN) && (remote == remote_)) {
+ LOG_F(LS_VERBOSE) << "Reusing connection to: " << remote_;
+ } else {
+ remote_ = remote;
+ stream_->Close();
+ if ((stream_->GetSocket()->Connect(remote_) != 0)
+ && !stream_->GetSocket()->IsBlocking()) {
+ if (err)
+ *err = stream_->GetSocket()->GetError();
+ return NULL;
+ } else {
+ LOG_F(LS_VERBOSE) << "Opening connection to: " << remote_;
+ }
+ }
+ stream_->SignalEvent.disconnect(this);
+ checked_out_ = true;
+ if (err)
+ *err = 0;
+ return stream_;
+}
+
+void
+ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) {
+ ASSERT(stream == stream_);
+ ASSERT(checked_out_);
+ checked_out_ = false;
+ // Until the socket is reused, monitor it to determine if it closes.
+ stream_->SignalEvent.connect(this, &ReuseSocketPool::OnStreamEvent);
+}
+
+void
+ReuseSocketPool::OnStreamEvent(StreamInterface* stream, int events, int err) {
+ ASSERT(stream == stream_);
+ ASSERT(!checked_out_);
+
+ // If the stream was written to and then immediately returned to us then
+ // we may get a writable notification for it, which we should ignore.
+ if (events == SE_WRITE) {
+ LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly writable: ignoring";
+ return;
+ }
+
+ // If the peer sent data, we can't process it, so drop the connection.
+ // If the socket has closed, clean it up.
+ // In either case, we'll reconnect it the next time it is used.
+ ASSERT(0 != (events & (SE_READ|SE_CLOSE)));
+ if (0 != (events & SE_CLOSE)) {
+ LOG_F(LS_VERBOSE) << "Connection closed with error: " << err;
+ } else {
+ LOG_F(LS_VERBOSE) << "Pooled Socket unexpectedly readable: closing";
+ }
+ stream_->Close();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingPoolAdapter::LoggingPoolAdapter(
+ StreamPool* pool, LoggingSeverity level, const std::string& label,
+ bool binary_mode)
+ : pool_(pool), level_(level), label_(label), binary_mode_(binary_mode) {
+}
+
+LoggingPoolAdapter::~LoggingPoolAdapter() {
+ for (StreamList::iterator it = recycle_bin_.begin();
+ it != recycle_bin_.end(); ++it) {
+ delete *it;
+ }
+}
+
+StreamInterface* LoggingPoolAdapter::RequestConnectedStream(
+ const SocketAddress& remote, int* err) {
+ if (StreamInterface* stream = pool_->RequestConnectedStream(remote, err)) {
+ ASSERT(SS_CLOSED != stream->GetState());
+ std::stringstream ss;
+ ss << label_ << "(0x" << std::setfill('0') << std::hex << std::setw(8)
+ << stream << ")";
+ LOG_V(level_) << ss.str()
+ << ((SS_OPEN == stream->GetState()) ? " Connected"
+ : " Connecting")
+ << " to " << remote;
+ if (recycle_bin_.empty()) {
+ return new LoggingAdapter(stream, level_, ss.str(), binary_mode_);
+ }
+ LoggingAdapter* logging = recycle_bin_.front();
+ recycle_bin_.pop_front();
+ logging->set_label(ss.str());
+ logging->Attach(stream);
+ return logging;
+ }
+ return NULL;
+}
+
+void LoggingPoolAdapter::ReturnConnectedStream(StreamInterface* stream) {
+ LoggingAdapter* logging = static_cast<LoggingAdapter*>(stream);
+ pool_->ReturnConnectedStream(logging->Detach());
+ recycle_bin_.push_back(logging);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/socketpool.h b/talk/base/socketpool.h
new file mode 100644
index 0000000..847d8ff
--- /dev/null
+++ b/talk/base/socketpool.h
@@ -0,0 +1,160 @@
+/*
+ * 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_SOCKETPOOL_H_
+#define TALK_BASE_SOCKETPOOL_H_
+
+#include <deque>
+#include <list>
+#include "talk/base/logging.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+
+namespace talk_base {
+
+class AsyncSocket;
+class LoggingAdapter;
+class SocketFactory;
+class SocketStream;
+class StreamInterface;
+
+//////////////////////////////////////////////////////////////////////
+// StreamPool
+//////////////////////////////////////////////////////////////////////
+
+class StreamPool {
+public:
+ virtual ~StreamPool() { }
+
+ virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+ int* err) = 0;
+ virtual void ReturnConnectedStream(StreamInterface* stream) = 0;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamCache - Caches a set of open streams, defers creation/destruction to
+// the supplied StreamPool.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamCache : public StreamPool, public sigslot::has_slots<> {
+public:
+ StreamCache(StreamPool* pool);
+ virtual ~StreamCache();
+
+ // StreamPool Interface
+ virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+ int* err);
+ virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+ typedef std::pair<SocketAddress, StreamInterface*> ConnectedStream;
+ typedef std::list<ConnectedStream> ConnectedList;
+
+ void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+ // We delegate stream creation and deletion to this pool.
+ StreamPool* pool_;
+ // Streams that are in use (returned from RequestConnectedStream).
+ ConnectedList active_;
+ // Streams which were returned to us, but are still open.
+ ConnectedList cached_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NewSocketPool
+// Creates a new stream on every request
+///////////////////////////////////////////////////////////////////////////////
+
+class NewSocketPool : public StreamPool {
+public:
+ NewSocketPool(SocketFactory* factory);
+ virtual ~NewSocketPool();
+
+ // StreamPool Interface
+ virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+ int* err);
+ virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+ SocketFactory* factory_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// ReuseSocketPool
+// Maintains a single socket at a time, and will reuse it without closing if
+// the destination address is the same.
+///////////////////////////////////////////////////////////////////////////////
+
+class ReuseSocketPool : public StreamPool, public sigslot::has_slots<> {
+public:
+ ReuseSocketPool(SocketFactory* factory);
+ virtual ~ReuseSocketPool();
+
+ // StreamPool Interface
+ virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+ int* err);
+ virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+ void OnStreamEvent(StreamInterface* stream, int events, int err);
+
+ SocketFactory* factory_;
+ SocketStream* stream_;
+ SocketAddress remote_;
+ bool checked_out_; // Whether the stream is currently checked out
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingPoolAdapter - Adapts a StreamPool to supply streams with attached
+// LoggingAdapters.
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingPoolAdapter : public StreamPool {
+public:
+ LoggingPoolAdapter(StreamPool* pool, LoggingSeverity level,
+ const std::string& label, bool binary_mode);
+ virtual ~LoggingPoolAdapter();
+
+ // StreamPool Interface
+ virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote,
+ int* err);
+ virtual void ReturnConnectedStream(StreamInterface* stream);
+
+private:
+ StreamPool* pool_;
+ LoggingSeverity level_;
+ std::string label_;
+ bool binary_mode_;
+ typedef std::deque<LoggingAdapter*> StreamList;
+ StreamList recycle_bin_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETPOOL_H_
diff --git a/talk/base/socketserver.h b/talk/base/socketserver.h
new file mode 100644
index 0000000..151ce61
--- /dev/null
+++ b/talk/base/socketserver.h
@@ -0,0 +1,61 @@
+/*
+ * 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_SOCKETSERVER_H_
+#define TALK_BASE_SOCKETSERVER_H_
+
+#include "talk/base/socketfactory.h"
+
+namespace talk_base {
+
+class MessageQueue;
+
+// Provides the ability to wait for activity on a set of sockets. The Thread
+// class provides a nice wrapper on a socket server.
+//
+// The server is also a socket factory. The sockets it creates will be
+// notified of asynchronous I/O from this server's Wait method.
+class SocketServer : public SocketFactory {
+ public:
+ // When the socket server is installed into a Thread, this function is
+ // called to allow the socket server to use the thread's message queue for
+ // any messaging that it might need to perform.
+ virtual void SetMessageQueue(MessageQueue* queue) {}
+
+ // Sleeps until:
+ // 1) cms milliseconds have elapsed (unless cms == kForever)
+ // 2) WakeUp() is called
+ // While sleeping, I/O is performed if process_io is true.
+ virtual bool Wait(int cms, bool process_io) = 0;
+
+ // Causes the current wait (if one is in progress) to wake up.
+ virtual void WakeUp() = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETSERVER_H_
diff --git a/talk/base/socketstream.cc b/talk/base/socketstream.cc
new file mode 100644
index 0000000..3dc5a95
--- /dev/null
+++ b/talk/base/socketstream.cc
@@ -0,0 +1,138 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/socketstream.h"
+
+namespace talk_base {
+
+SocketStream::SocketStream(AsyncSocket* socket) : socket_(NULL) {
+ Attach(socket);
+}
+
+SocketStream::~SocketStream() {
+ delete socket_;
+}
+
+void SocketStream::Attach(AsyncSocket* socket) {
+ if (socket_)
+ delete socket_;
+ socket_ = socket;
+ if (socket_) {
+ socket_->SignalConnectEvent.connect(this, &SocketStream::OnConnectEvent);
+ socket_->SignalReadEvent.connect(this, &SocketStream::OnReadEvent);
+ socket_->SignalWriteEvent.connect(this, &SocketStream::OnWriteEvent);
+ socket_->SignalCloseEvent.connect(this, &SocketStream::OnCloseEvent);
+ }
+}
+
+AsyncSocket* SocketStream::Detach() {
+ AsyncSocket* socket = socket_;
+ if (socket_) {
+ socket_->SignalConnectEvent.disconnect(this);
+ socket_->SignalReadEvent.disconnect(this);
+ socket_->SignalWriteEvent.disconnect(this);
+ socket_->SignalCloseEvent.disconnect(this);
+ socket_ = NULL;
+ }
+ return socket;
+}
+
+StreamState SocketStream::GetState() const {
+ ASSERT(socket_ != NULL);
+ switch (socket_->GetState()) {
+ case Socket::CS_CONNECTED:
+ return SS_OPEN;
+ case Socket::CS_CONNECTING:
+ return SS_OPENING;
+ case Socket::CS_CLOSED:
+ default:
+ return SS_CLOSED;
+ }
+}
+
+StreamResult SocketStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ ASSERT(socket_ != NULL);
+ int result = socket_->Recv(buffer, buffer_len);
+ if (result < 0) {
+ if (socket_->IsBlocking())
+ return SR_BLOCK;
+ if (error)
+ *error = socket_->GetError();
+ return SR_ERROR;
+ }
+ if ((result > 0) || (buffer_len == 0)) {
+ if (read)
+ *read = result;
+ return SR_SUCCESS;
+ }
+ return SR_EOS;
+}
+
+StreamResult SocketStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ ASSERT(socket_ != NULL);
+ int result = socket_->Send(data, data_len);
+ if (result < 0) {
+ if (socket_->IsBlocking())
+ return SR_BLOCK;
+ if (error)
+ *error = socket_->GetError();
+ return SR_ERROR;
+ }
+ if (written)
+ *written = result;
+ return SR_SUCCESS;
+}
+
+void SocketStream::Close() {
+ ASSERT(socket_ != NULL);
+ socket_->Close();
+}
+
+void SocketStream::OnConnectEvent(AsyncSocket* socket) {
+ ASSERT(socket == socket_);
+ SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0);
+}
+
+void SocketStream::OnReadEvent(AsyncSocket* socket) {
+ ASSERT(socket == socket_);
+ SignalEvent(this, SE_READ, 0);
+}
+
+void SocketStream::OnWriteEvent(AsyncSocket* socket) {
+ ASSERT(socket == socket_);
+ SignalEvent(this, SE_WRITE, 0);
+}
+
+void SocketStream::OnCloseEvent(AsyncSocket* socket, int err) {
+ ASSERT(socket == socket_);
+ SignalEvent(this, SE_CLOSE, err);
+}
+
+
+} // namespace talk_base
diff --git a/talk/base/socketstream.h b/talk/base/socketstream.h
new file mode 100644
index 0000000..591dc4c
--- /dev/null
+++ b/talk/base/socketstream.h
@@ -0,0 +1,74 @@
+/*
+ * libjingle
+ * Copyright 2005--2010, 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_SOCKETSTREAM_H_
+#define TALK_BASE_SOCKETSTREAM_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/common.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SocketStream : public StreamInterface, public sigslot::has_slots<> {
+ public:
+ explicit SocketStream(AsyncSocket* socket);
+ virtual ~SocketStream();
+
+ void Attach(AsyncSocket* socket);
+ AsyncSocket* Detach();
+
+ AsyncSocket* GetSocket() { return socket_; }
+
+ virtual StreamState GetState() const;
+
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+
+ virtual void Close();
+
+ private:
+ void OnConnectEvent(AsyncSocket* socket);
+ void OnReadEvent(AsyncSocket* socket);
+ void OnWriteEvent(AsyncSocket* socket);
+ void OnCloseEvent(AsyncSocket* socket, int err);
+
+ AsyncSocket* socket_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SocketStream);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SOCKETSTREAM_H_
diff --git a/talk/base/ssladapter.cc b/talk/base/ssladapter.cc
new file mode 100644
index 0000000..2368e7a
--- /dev/null
+++ b/talk/base/ssladapter.cc
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif // !WIN32
+#endif
+
+#include "talk/base/ssladapter.h"
+
+#if SSL_USE_SCHANNEL
+
+#include "schanneladapter.h"
+
+#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL
+
+#include "openssladapter.h"
+
+#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLAdapter*
+SSLAdapter::Create(AsyncSocket* socket) {
+#if SSL_USE_SCHANNEL
+ return new SChannelAdapter(socket);
+#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL
+ return new OpenSSLAdapter(socket);
+#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+ return NULL;
+#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+ return OpenSSLAdapter::InitializeSSL(callback);
+}
+
+bool InitializeSSLThread() {
+ return OpenSSLAdapter::InitializeSSLThread();
+}
+
+bool CleanupSSL() {
+ return OpenSSLAdapter::CleanupSSL();
+}
+
+#else // !SSL_USE_OPENSSL
+
+bool InitializeSSL(VerificationCallback callback) {
+ return true;
+}
+
+bool InitializeSSLThread() {
+ return true;
+}
+
+bool CleanupSSL() {
+ return true;
+}
+
+#endif // !SSL_USE_OPENSSL
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/ssladapter.h b/talk/base/ssladapter.h
new file mode 100644
index 0000000..687cc24
--- /dev/null
+++ b/talk/base/ssladapter.h
@@ -0,0 +1,76 @@
+/*
+ * 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_SSLADAPTER_H__
+#define TALK_BASE_SSLADAPTER_H__
+
+#include "talk/base/asyncsocket.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+class SSLAdapter : public AsyncSocketAdapter {
+public:
+ SSLAdapter(AsyncSocket* socket)
+ : AsyncSocketAdapter(socket), ignore_bad_cert_(false) { }
+
+ bool ignore_bad_cert() const { return ignore_bad_cert_; }
+ void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+
+ // StartSSL returns 0 if successful.
+ // If StartSSL is called while the socket is closed or connecting, the SSL
+ // negotiation will begin as soon as the socket connects.
+ virtual int StartSSL(const char* hostname, bool restartable) = 0;
+
+ // Create the default SSL adapter for this platform
+ static SSLAdapter* Create(AsyncSocket* socket);
+
+private:
+ // If true, the server certificate need not match the configured hostname.
+ bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+typedef bool (*VerificationCallback)(void* cert);
+
+// Call this on the main thread, before using SSL.
+// Call CleanupSSLThread when finished with SSL.
+bool InitializeSSL(VerificationCallback callback = NULL);
+
+// Call to initialize additional threads.
+bool InitializeSSLThread();
+
+// Call to cleanup additional threads, and also the main thread.
+bool CleanupSSL();
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SSLADAPTER_H__
diff --git a/talk/base/sslidentity.cc b/talk/base/sslidentity.cc
new file mode 100644
index 0000000..665e700
--- /dev/null
+++ b/talk/base/sslidentity.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+
+#include <string>
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif // !WIN32
+#endif
+
+#include "talk/base/sslidentity.h"
+
+#if SSL_USE_SCHANNEL
+
+#error "Not implemented yet"
+
+#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslidentity.h"
+
+namespace talk_base {
+
+SSLCertificate* SSLCertificate::FromPEMString(const std::string& pem_string,
+ int* pem_length) {
+ return OpenSSLCertificate::FromPEMString(pem_string, pem_length);
+}
+
+SSLIdentity* SSLIdentity::Generate(const std::string& common_name) {
+ return OpenSSLIdentity::Generate(common_name);
+}
+
+} // namespace talk_base
+
+#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+#error "No SSL implementation"
+
+#endif // SSL_USE_OPENSSL/!SSL_USE_SCHANNEL
diff --git a/talk/base/sslidentity.h b/talk/base/sslidentity.h
new file mode 100644
index 0000000..ed996b4
--- /dev/null
+++ b/talk/base/sslidentity.h
@@ -0,0 +1,91 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Handling of certificates and keypairs for SSLStreamAdapter's peer mode.
+
+#ifndef TALK_BASE_SSLIDENTITY_H__
+#define TALK_BASE_SSLIDENTITY_H__
+
+#include <string>
+
+namespace talk_base {
+
+// Abstract interface overridden by SSL library specific
+// implementations.
+
+// A somewhat opaque type used to encapsulate a certificate.
+// Wraps the SSL library's notion of a certificate, with reference counting.
+// The SSLCertificate object is pretty much immutable once created.
+// (The OpenSSL implementation only does reference counting and
+// possibly caching of intermediate results.)
+class SSLCertificate {
+ public:
+ // Parses and build a certificate from a PEM encoded string.
+ // Returns NULL on failure.
+ // The length of the string representation of the certificate is
+ // stored in *pem_length if it is non-NULL, and only if
+ // parsing was successful.
+ // Caller is responsible for freeing the returned object.
+ static SSLCertificate* FromPEMString(const std::string& pem_string,
+ int* pem_length);
+ virtual ~SSLCertificate() {}
+
+ // Returns a new SSLCertificate object instance wrapping the same
+ // underlying certificate.
+ // Caller is responsible for freeing the returned object.
+ virtual SSLCertificate* GetReference() = 0;
+
+ // Returns a PEM encoded string representation of the certificate.
+ virtual std::string ToPEMString() const = 0;
+};
+
+// Our identity in an SSL negotiation: a keypair and certificate (both
+// with the same public key).
+// This too is pretty much immutable once created.
+class SSLIdentity {
+ public:
+ // Generates an identity (keypair and self-signed certificate). If
+ // common_name is non-empty, it will be used for the certificate's
+ // subject and issuer name, otherwise a random string will be used.
+ // Returns NULL on failure.
+ // Caller is responsible for freeing the returned object.
+ static SSLIdentity* Generate(const std::string& common_name);
+
+ virtual ~SSLIdentity() {}
+
+ // Returns a new SSLIdentity object instance wrapping the same
+ // identity information.
+ // Caller is responsible for freeing the returned object.
+ virtual SSLIdentity* GetReference() = 0;
+
+ // Returns a temporary reference to the certificate.
+ virtual SSLCertificate& certificate() const = 0;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SSLIDENTITY_H__
diff --git a/talk/base/sslsocketfactory.cc b/talk/base/sslsocketfactory.cc
new file mode 100644
index 0000000..fcb2c0c
--- /dev/null
+++ b/talk/base/sslsocketfactory.cc
@@ -0,0 +1,181 @@
+/*
+ * libjingle
+ * Copyright 2007, 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/httpcommon.h"
+#include "talk/base/httpcommon-inl.h"
+#include "talk/base/socketadapters.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/sslsocketfactory.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// ProxySocketAdapter
+// TODO: Consider combining AutoDetectProxy and ProxySocketAdapter. I think
+// the socket adapter is the more appropriate idiom for automatic proxy
+// detection. We may or may not want to combine proxydetect.* as well.
+///////////////////////////////////////////////////////////////////////////////
+
+class ProxySocketAdapter : public AsyncSocketAdapter {
+ public:
+ ProxySocketAdapter(SslSocketFactory* factory, int type)
+ : AsyncSocketAdapter(NULL), factory_(factory), type_(type),
+ detect_(NULL) {
+ }
+ virtual ~ProxySocketAdapter() {
+ Close();
+ }
+
+ virtual int Connect(const SocketAddress& addr) {
+ ASSERT(NULL == detect_);
+ ASSERT(NULL == socket_);
+ remote_ = addr;
+ if (remote_.IsAnyIP() && remote_.hostname().empty()) {
+ LOG_F(LS_ERROR) << "Empty address";
+ return SOCKET_ERROR;
+ }
+ Url<char> url("/", remote_.IPAsString(), remote_.port());
+ detect_ = new AutoDetectProxy(factory_->agent_);
+ detect_->set_server_url(url.url());
+ detect_->SignalWorkDone.connect(this,
+ &ProxySocketAdapter::OnProxyDetectionComplete);
+ detect_->Start();
+ return SOCKET_ERROR;
+ }
+ virtual int GetError() const {
+ if (socket_) {
+ return socket_->GetError();
+ }
+ return detect_ ? EWOULDBLOCK : EADDRNOTAVAIL;
+ }
+ virtual int Close() {
+ if (socket_) {
+ return socket_->Close();
+ }
+ if (detect_) {
+ detect_->Destroy(false);
+ detect_ = NULL;
+ }
+ return 0;
+ }
+ virtual ConnState GetState() const {
+ if (socket_) {
+ return socket_->GetState();
+ }
+ return detect_ ? CS_CONNECTING : CS_CLOSED;
+ }
+
+private:
+ // AutoDetectProxy Slots
+ void OnProxyDetectionComplete(SignalThread* thread) {
+ ASSERT(detect_ == thread);
+ Attach(factory_->CreateProxySocket(detect_->proxy(), type_));
+ detect_->Release();
+ detect_ = NULL;
+ if (0 == AsyncSocketAdapter::Connect(remote_)) {
+ SignalConnectEvent(this);
+ } else if (!IsBlockingError(socket_->GetError())) {
+ SignalCloseEvent(this, socket_->GetError());
+ }
+ }
+
+ SslSocketFactory* factory_;
+ int type_;
+ SocketAddress remote_;
+ AutoDetectProxy* detect_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+Socket* SslSocketFactory::CreateSocket(int type) {
+ return factory_->CreateSocket(type);
+}
+
+AsyncSocket* SslSocketFactory::CreateAsyncSocket(int type) {
+ if (autodetect_proxy_) {
+ return new ProxySocketAdapter(this, type);
+ } else {
+ return CreateProxySocket(proxy_, type);
+ }
+}
+
+AsyncSocket* SslSocketFactory::CreateProxySocket(const ProxyInfo& proxy,
+ int type) {
+ AsyncSocket* socket = factory_->CreateAsyncSocket(type);
+ if (!socket)
+ return NULL;
+
+ // Binary logging happens at the lowest level
+ if (!logging_label_.empty() && binary_mode_) {
+ socket = new LoggingSocketAdapter(socket, logging_level_,
+ logging_label_.c_str(), binary_mode_);
+ }
+
+ if (proxy.type) {
+ AsyncSocket* proxy_socket = 0;
+ if (proxy_.type == PROXY_SOCKS5) {
+ proxy_socket = new AsyncSocksProxySocket(socket, proxy.address,
+ proxy.username, proxy.password);
+ } else {
+ // Note: we are trying unknown proxies as HTTPS currently
+ AsyncHttpsProxySocket* http_proxy =
+ new AsyncHttpsProxySocket(socket, agent_, proxy.address,
+ proxy.username, proxy.password);
+ http_proxy->SetForceConnect(force_connect_ || !hostname_.empty());
+ proxy_socket = http_proxy;
+ }
+ if (!proxy_socket) {
+ delete socket;
+ return NULL;
+ }
+ socket = proxy_socket; // for our purposes the proxy is now the socket
+ }
+
+ if (!hostname_.empty()) {
+ if (SSLAdapter* ssl_adapter = SSLAdapter::Create(socket)) {
+ ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_);
+ ssl_adapter->StartSSL(hostname_.c_str(), true);
+ socket = ssl_adapter;
+ } else {
+ LOG_F(LS_ERROR) << "SSL unavailable";
+ }
+ }
+
+ // Regular logging occurs at the highest level
+ if (!logging_label_.empty() && !binary_mode_) {
+ socket = new LoggingSocketAdapter(socket, logging_level_,
+ logging_label_.c_str(), binary_mode_);
+ }
+ return socket;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/sslsocketfactory.h b/talk/base/sslsocketfactory.h
new file mode 100644
index 0000000..d42689a
--- /dev/null
+++ b/talk/base/sslsocketfactory.h
@@ -0,0 +1,94 @@
+/*
+ * libjingle
+ * Copyright 2007, 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_SSLSOCKETFACTORY_H__
+#define TALK_BASE_SSLSOCKETFACTORY_H__
+
+#include "talk/base/proxyinfo.h"
+#include "talk/base/socketserver.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// SslSocketFactory
+///////////////////////////////////////////////////////////////////////////////
+
+class SslSocketFactory : public SocketFactory {
+ public:
+ SslSocketFactory(SocketFactory* factory, const std::string& user_agent)
+ : factory_(factory), agent_(user_agent), autodetect_proxy_(true),
+ force_connect_(false), logging_level_(LS_VERBOSE), binary_mode_(false) {
+ }
+
+ void SetAutoDetectProxy() {
+ autodetect_proxy_ = true;
+ }
+ void SetForceConnect(bool force) {
+ force_connect_ = force;
+ }
+ void SetProxy(const ProxyInfo& proxy) {
+ autodetect_proxy_ = false;
+ proxy_ = proxy;
+ }
+ bool autodetect_proxy() const { return autodetect_proxy_; }
+ const ProxyInfo& proxy() const { return proxy_; }
+
+ void UseSSL(const char* hostname) { hostname_ = hostname; }
+ void DisableSSL() { hostname_.clear(); }
+ void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; }
+ bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+ void SetLogging(LoggingSeverity level, const std::string& label,
+ bool binary_mode = false) {
+ logging_level_ = level;
+ logging_label_ = label;
+ binary_mode_ = binary_mode;
+ }
+
+ // SocketFactory Interface
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+
+ private:
+ friend class ProxySocketAdapter;
+ AsyncSocket* CreateProxySocket(const ProxyInfo& proxy, int type);
+
+ SocketFactory* factory_;
+ std::string agent_;
+ bool autodetect_proxy_, force_connect_;
+ ProxyInfo proxy_;
+ std::string hostname_, logging_label_;
+ LoggingSeverity logging_level_;
+ bool binary_mode_;
+ bool ignore_bad_cert_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SSLSOCKETFACTORY_H__
diff --git a/talk/base/sslstreamadapter.cc b/talk/base/sslstreamadapter.cc
new file mode 100644
index 0000000..17121fd
--- /dev/null
+++ b/talk/base/sslstreamadapter.cc
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif // HAVE_CONFIG_H
+
+// Decide which (if any) implementation of SSL we will use.
+#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL)
+#ifdef WIN32
+#define SSL_USE_SCHANNEL 1
+#else // !WIN32
+#define SSL_USE_OPENSSL HAVE_OPENSSL_SSL_H
+#endif // !WIN32
+#endif
+
+#include "talk/base/sslstreamadapter.h"
+
+#if SSL_USE_SCHANNEL
+
+#error "Not implemented yet"
+
+#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL
+
+#include "talk/base/opensslstreamadapter.h"
+
+#endif // SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+SSLStreamAdapter* SSLStreamAdapter::Create(StreamInterface* stream) {
+#if SSL_USE_SCHANNEL
+ // not implemented yet
+ // return new SChannelStreamAdapter(stream);
+#elif SSL_USE_OPENSSL // && !SSL_USE_SCHANNEL
+ return new OpenSSLStreamAdapter(stream);
+#else // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+ return NULL;
+#endif // !SSL_USE_OPENSSL && !SSL_USE_SCHANNEL
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/sslstreamadapter.h b/talk/base/sslstreamadapter.h
new file mode 100644
index 0000000..62cef08
--- /dev/null
+++ b/talk/base/sslstreamadapter.h
@@ -0,0 +1,123 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_BASE_SSLSTREAMADAPTER_H__
+#define TALK_BASE_SSLSTREAMADAPTER_H__
+
+#include "talk/base/stream.h"
+#include "talk/base/sslidentity.h"
+
+namespace talk_base {
+
+// SSLStreamAdapter : A StreamInterfaceAdapter that does SSL/TLS.
+// After SSL has been started, the stream will only open on successful
+// SSL verification of certificates, and the communication is
+// encrypted of course.
+//
+// This class was written with SSLAdapter as a starting point. It
+// offers a similar interface, with two differences: there is no
+// support for a restartable SSL connection, and this class has a
+// peer-to-peer mode.
+//
+// The SSL library requires initialization and cleanup. Static method
+// for doing this are in SSLAdapter. They should possibly be moved out
+// to a neutral class.
+
+class SSLStreamAdapter : public StreamAdapterInterface {
+ public:
+ // Instantiate an SSLStreamAdapter wrapping the given stream,
+ // (using the selected implementation for the platform).
+ // Caller is responsible for freeing the returned object.
+ static SSLStreamAdapter* Create(StreamInterface* stream);
+
+ explicit SSLStreamAdapter(StreamInterface* stream)
+ : StreamAdapterInterface(stream), ignore_bad_cert_(false) { }
+
+ void set_ignore_bad_cert(bool ignore) { ignore_bad_cert_ = ignore; }
+ bool ignore_bad_cert() const { return ignore_bad_cert_; }
+
+ // Specify our SSL identity: key and certificate. Mostly this is
+ // only used in the peer-to-peer mode (unless we actually want to
+ // provide a client certificate to a server).
+ // SSLStream takes ownership of the SSLIdentity object and will
+ // free it when appropriate. Should be called no more than once on a
+ // given SSLStream instance.
+ virtual void SetIdentity(SSLIdentity* identity) = 0;
+
+ // Call this to indicate that we are to play the server's role in
+ // the peer-to-peer mode.
+ virtual void SetServerRole() = 0;
+
+ // The mode of operation is selected by calling either
+ // StartSSLWithServer or StartSSLWithPeer.
+ // Use of the stream prior to calling either of these functions will
+ // pass data in clear text.
+ // Calling one of these functions causes SSL negotiation to begin as
+ // soon as possible: right away if the underlying wrapped stream is
+ // already opened, or else as soon as it opens.
+ //
+ // These functions return a negative error code on failure.
+ // Returning 0 means success so far, but negotiation is probably not
+ // complete and will continue asynchronously. In that case, the
+ // exposed stream will open after successful negotiation and
+ // verification, or an SE_CLOSE event will be raised if negotiation
+ // fails.
+
+ // StartSSLWithServer starts SSL negotiation with a server in
+ // traditional mode. server_name specifies the expected server name
+ // which the server's certificate needs to specify.
+ virtual int StartSSLWithServer(const char* server_name) = 0;
+
+ // StartSSLWithPeer starts negotiation in the special peer-to-peer
+ // mode.
+ // Generally, SetIdentity() and possibly SetServerRole() should have
+ // been called before this.
+ // SetPeerCertificate() must also be called. It may be called after
+ // StartSSLWithPeer() but must be called before the underlying
+ // stream opens.
+ virtual int StartSSLWithPeer() = 0;
+
+ // Specify the certificate that our peer is expected to use in
+ // peer-to-peer mode. Only this certificate will be accepted during
+ // SSL verification. The certificate is assumed to have been
+ // obtained through some other secure channel (such as the XMPP
+ // channel). (This could also specify the certificate authority that
+ // will sign the peer's certificate.)
+ // SSLStream takes ownership of the SSLCertificate object and will
+ // free it when appropriate. Should be called no more than once on a
+ // given SSLStream instance.
+ virtual void SetPeerCertificate(SSLCertificate* cert) = 0;
+
+ // If true, the server certificate need not match the configured
+ // server_name, and in fact missing certificate authority and other
+ // verification errors are ignored.
+ bool ignore_bad_cert_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_SSLSTREAMADAPTER_H__
diff --git a/talk/base/stream.cc b/talk/base/stream.cc
new file mode 100644
index 0000000..755fb08
--- /dev/null
+++ b/talk/base/stream.cc
@@ -0,0 +1,1094 @@
+/*
+ * 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.
+ */
+
+#if defined(POSIX)
+#include <sys/file.h>
+#endif // POSIX
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#define fileno _fileno
+#endif
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface
+///////////////////////////////////////////////////////////////////////////////
+
+enum {
+ MSG_POST_EVENT = 0xF1F1
+};
+
+StreamInterface::~StreamInterface() {
+}
+
+struct PostEventData : public MessageData {
+ int events, error;
+ PostEventData(int ev, int er) : events(ev), error(er) { }
+};
+
+StreamResult StreamInterface::WriteAll(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ StreamResult result = SR_SUCCESS;
+ size_t total_written = 0, current_written;
+ while (total_written < data_len) {
+ result = Write(static_cast<const char*>(data) + total_written,
+ data_len - total_written, ¤t_written, error);
+ if (result != SR_SUCCESS)
+ break;
+ total_written += current_written;
+ }
+ if (written)
+ *written = total_written;
+ return result;
+}
+
+StreamResult StreamInterface::ReadAll(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ StreamResult result = SR_SUCCESS;
+ size_t total_read = 0, current_read;
+ while (total_read < buffer_len) {
+ result = Read(static_cast<char*>(buffer) + total_read,
+ buffer_len - total_read, ¤t_read, error);
+ if (result != SR_SUCCESS)
+ break;
+ total_read += current_read;
+ }
+ if (read)
+ *read = total_read;
+ return result;
+}
+
+StreamResult StreamInterface::ReadLine(std::string* line) {
+ line->clear();
+ StreamResult result = SR_SUCCESS;
+ while (true) {
+ char ch;
+ result = Read(&ch, sizeof(ch), NULL, NULL);
+ if (result != SR_SUCCESS) {
+ break;
+ }
+ if (ch == '\n') {
+ break;
+ }
+ line->push_back(ch);
+ }
+ if (!line->empty()) { // give back the line we've collected so far with
+ result = SR_SUCCESS; // a success code. Otherwise return the last code
+ }
+ return result;
+}
+
+void StreamInterface::PostEvent(Thread* t, int events, int err) {
+ t->Post(this, MSG_POST_EVENT, new PostEventData(events, err));
+}
+
+void StreamInterface::PostEvent(int events, int err) {
+ PostEvent(Thread::Current(), events, err);
+}
+
+StreamInterface::StreamInterface() {
+}
+
+void StreamInterface::OnMessage(Message* msg) {
+ if (MSG_POST_EVENT == msg->message_id) {
+ PostEventData* pe = static_cast<PostEventData*>(msg->pdata);
+ SignalEvent(this, pe->events, pe->error);
+ delete msg->pdata;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface
+///////////////////////////////////////////////////////////////////////////////
+
+StreamAdapterInterface::StreamAdapterInterface(StreamInterface* stream,
+ bool owned)
+ : stream_(stream), owned_(owned) {
+ if (NULL != stream_)
+ stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+void StreamAdapterInterface::Attach(StreamInterface* stream, bool owned) {
+ if (NULL != stream_)
+ stream_->SignalEvent.disconnect(this);
+ if (owned_)
+ delete stream_;
+ stream_ = stream;
+ owned_ = owned;
+ if (NULL != stream_)
+ stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent);
+}
+
+StreamInterface* StreamAdapterInterface::Detach() {
+ if (NULL != stream_)
+ stream_->SignalEvent.disconnect(this);
+ StreamInterface* stream = stream_;
+ stream_ = NULL;
+ return stream;
+}
+
+StreamAdapterInterface::~StreamAdapterInterface() {
+ if (owned_)
+ delete stream_;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap
+///////////////////////////////////////////////////////////////////////////////
+
+StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
+: StreamAdapterInterface(stream), tap_(NULL), tap_result_(SR_SUCCESS),
+ tap_error_(0)
+{
+ AttachTap(tap);
+}
+
+void StreamTap::AttachTap(StreamInterface* tap) {
+ tap_.reset(tap);
+}
+
+StreamInterface* StreamTap::DetachTap() {
+ return tap_.release();
+}
+
+StreamResult StreamTap::GetTapResult(int* error) {
+ if (error) {
+ *error = tap_error_;
+ }
+ return tap_result_;
+}
+
+StreamResult StreamTap::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ size_t backup_read;
+ if (!read) {
+ read = &backup_read;
+ }
+ StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len,
+ read, error);
+ if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+ tap_result_ = tap_->WriteAll(buffer, *read, NULL, &tap_error_);
+ }
+ return res;
+}
+
+StreamResult StreamTap::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ size_t backup_written;
+ if (!written) {
+ written = &backup_written;
+ }
+ StreamResult res = StreamAdapterInterface::Write(data, data_len,
+ written, error);
+ if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
+ tap_result_ = tap_->WriteAll(data, *written, NULL, &tap_error_);
+ }
+ return res;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment
+///////////////////////////////////////////////////////////////////////////////
+
+StreamSegment::StreamSegment(StreamInterface* stream)
+: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+ length_(SIZE_UNKNOWN)
+{
+ // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+ stream->GetPosition(&start_);
+}
+
+StreamSegment::StreamSegment(StreamInterface* stream, size_t length)
+: StreamAdapterInterface(stream), start_(SIZE_UNKNOWN), pos_(0),
+ length_(length)
+{
+ // It's ok for this to fail, in which case start_ is left as SIZE_UNKNOWN.
+ stream->GetPosition(&start_);
+}
+
+StreamResult StreamSegment::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error)
+{
+ if (SIZE_UNKNOWN != length_) {
+ if (pos_ >= length_)
+ return SR_EOS;
+ buffer_len = _min(buffer_len, length_ - pos_);
+ }
+ size_t backup_read;
+ if (!read) {
+ read = &backup_read;
+ }
+ StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len,
+ read, error);
+ if (SR_SUCCESS == result) {
+ pos_ += *read;
+ }
+ return result;
+}
+
+bool StreamSegment::SetPosition(size_t position) {
+ if (SIZE_UNKNOWN == start_)
+ return false; // Not seekable
+ if ((SIZE_UNKNOWN != length_) && (position > length_))
+ return false; // Seek past end of segment
+ if (!StreamAdapterInterface::SetPosition(start_ + position))
+ return false;
+ pos_ = position;
+ return true;
+}
+
+bool StreamSegment::GetPosition(size_t* position) const {
+ if (SIZE_UNKNOWN == start_)
+ return false; // Not seekable
+ if (!StreamAdapterInterface::GetPosition(position))
+ return false;
+ if (position) {
+ ASSERT(*position >= start_);
+ *position -= start_;
+ }
+ return true;
+}
+
+bool StreamSegment::GetSize(size_t* size) const {
+ if (!StreamAdapterInterface::GetSize(size))
+ return false;
+ if (size) {
+ if (SIZE_UNKNOWN != start_) {
+ ASSERT(*size >= start_);
+ *size -= start_;
+ }
+ if (SIZE_UNKNOWN != length_) {
+ *size = _min(*size, length_);
+ }
+ }
+ return true;
+}
+
+bool StreamSegment::GetAvailable(size_t* size) const {
+ if (!StreamAdapterInterface::GetAvailable(size))
+ return false;
+ if (size && (SIZE_UNKNOWN != length_))
+ *size = _min(*size, length_ - pos_);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream
+///////////////////////////////////////////////////////////////////////////////
+
+NullStream::NullStream() {
+}
+
+NullStream::~NullStream() {
+}
+
+StreamState NullStream::GetState() const {
+ return SS_OPEN;
+}
+
+StreamResult NullStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (error) *error = -1;
+ return SR_ERROR;
+}
+
+StreamResult NullStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (written) *written = data_len;
+ return SR_SUCCESS;
+}
+
+void NullStream::Close() {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream
+///////////////////////////////////////////////////////////////////////////////
+
+FileStream::FileStream() : file_(NULL) {
+}
+
+FileStream::~FileStream() {
+ FileStream::Close();
+}
+
+bool FileStream::Open(const std::string& filename, const char* mode) {
+ Close();
+#ifdef WIN32
+ std::wstring wfilename;
+ if (Utf8ToWindowsFilename(filename, &wfilename)) {
+ file_ = _wfopen(wfilename.c_str(), ToUtf16(mode).c_str());
+ } else {
+ file_ = NULL;
+ }
+#else
+ file_ = fopen(filename.c_str(), mode);
+#endif
+ return (file_ != NULL);
+}
+
+bool FileStream::OpenShare(const std::string& filename, const char* mode,
+ int shflag) {
+ Close();
+#ifdef WIN32
+ std::wstring wfilename;
+ if (Utf8ToWindowsFilename(filename, &wfilename)) {
+ file_ = _wfsopen(wfilename.c_str(), ToUtf16(mode).c_str(), shflag);
+ } else {
+ file_ = NULL;
+ }
+#else
+ return Open(filename, mode);
+#endif
+ return (file_ != NULL);
+}
+
+bool FileStream::DisableBuffering() {
+ if (!file_)
+ return false;
+ return (setvbuf(file_, NULL, _IONBF, 0) == 0);
+}
+
+StreamState FileStream::GetState() const {
+ return (file_ == NULL) ? SS_CLOSED : SS_OPEN;
+}
+
+StreamResult FileStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ if (!file_)
+ return SR_EOS;
+ size_t result = fread(buffer, 1, buffer_len, file_);
+ if ((result == 0) && (buffer_len > 0)) {
+ if (feof(file_))
+ return SR_EOS;
+ if (error)
+ *error = errno;
+ return SR_ERROR;
+ }
+ if (read)
+ *read = result;
+ return SR_SUCCESS;
+}
+
+StreamResult FileStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (!file_)
+ return SR_EOS;
+ size_t result = fwrite(data, 1, data_len, file_);
+ if ((result == 0) && (data_len > 0)) {
+ if (error)
+ *error = errno;
+ return SR_ERROR;
+ }
+ if (written)
+ *written = result;
+ return SR_SUCCESS;
+}
+
+void FileStream::Close() {
+ if (file_) {
+ DoClose();
+ file_ = NULL;
+ }
+}
+
+bool FileStream::SetPosition(size_t position) {
+ if (!file_)
+ return false;
+ return (fseek(file_, position, SEEK_SET) == 0);
+}
+
+bool FileStream::GetPosition(size_t* position) const {
+ ASSERT(NULL != position);
+ if (!file_)
+ return false;
+ long result = ftell(file_);
+ if (result < 0)
+ return false;
+ if (position)
+ *position = result;
+ return true;
+}
+
+bool FileStream::GetSize(size_t* size) const {
+ ASSERT(NULL != size);
+ if (!file_)
+ return false;
+ struct stat file_stats;
+ if (fstat(fileno(file_), &file_stats) != 0)
+ return false;
+ if (size)
+ *size = file_stats.st_size;
+ return true;
+}
+
+bool FileStream::GetAvailable(size_t* size) const {
+ ASSERT(NULL != size);
+ if (!GetSize(size))
+ return false;
+ long result = ftell(file_);
+ if (result < 0)
+ return false;
+ if (size)
+ *size -= result;
+ return true;
+}
+
+bool FileStream::ReserveSize(size_t size) {
+ // TODO: extend the file to the proper length
+ return true;
+}
+
+bool FileStream::GetSize(const std::string& filename, size_t* size) {
+ struct stat file_stats;
+ if (stat(filename.c_str(), &file_stats) != 0)
+ return false;
+ *size = file_stats.st_size;
+ return true;
+}
+
+bool FileStream::Flush() {
+ if (file_) {
+ return (0 == fflush(file_));
+ }
+ // try to flush empty file?
+ ASSERT(false);
+ return false;
+}
+
+#if defined(POSIX)
+
+bool FileStream::TryLock() {
+ if (file_ == NULL) {
+ // Stream not open.
+ ASSERT(false);
+ return false;
+ }
+
+ return flock(fileno(file_), LOCK_EX|LOCK_NB) == 0;
+}
+
+bool FileStream::Unlock() {
+ if (file_ == NULL) {
+ // Stream not open.
+ ASSERT(false);
+ return false;
+ }
+
+ return flock(fileno(file_), LOCK_UN) == 0;
+}
+
+#endif
+
+void FileStream::DoClose() {
+ fclose(file_);
+}
+
+#ifdef POSIX
+
+// Have to identically rewrite the FileStream destructor or else it would call
+// the base class's Close() instead of the sub-class's.
+POpenStream::~POpenStream() {
+ POpenStream::Close();
+}
+
+bool POpenStream::Open(const std::string& subcommand, const char* mode) {
+ Close();
+ file_ = popen(subcommand.c_str(), mode);
+ return file_ != NULL;
+}
+
+bool POpenStream::OpenShare(const std::string& subcommand, const char* mode,
+ int shflag) {
+ return Open(subcommand, mode);
+}
+
+void POpenStream::DoClose() {
+ wait_status_ = pclose(file_);
+}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStreamBase::MemoryStreamBase()
+ : buffer_(NULL), buffer_length_(0), data_length_(0),
+ seek_position_(0) {
+}
+
+StreamState MemoryStreamBase::GetState() const {
+ return SS_OPEN;
+}
+
+StreamResult MemoryStreamBase::Read(void* buffer, size_t bytes,
+ size_t* bytes_read, int* error) {
+ if (seek_position_ >= data_length_) {
+ return SR_EOS;
+ }
+ size_t available = data_length_ - seek_position_;
+ if (bytes > available) {
+ // Read partial buffer
+ bytes = available;
+ }
+ memcpy(buffer, &buffer_[seek_position_], bytes);
+ seek_position_ += bytes;
+ if (bytes_read) {
+ *bytes_read = bytes;
+ }
+ return SR_SUCCESS;
+}
+
+StreamResult MemoryStreamBase::Write(const void* buffer, size_t bytes,
+ size_t* bytes_written, int* error) {
+ size_t available = buffer_length_ - seek_position_;
+ if (0 == available) {
+ // Increase buffer size to the larger of:
+ // a) new position rounded up to next 256 bytes
+ // b) double the previous length
+ size_t new_buffer_length = _max(((seek_position_ + bytes) | 0xFF) + 1,
+ buffer_length_ * 2);
+ StreamResult result = DoReserve(new_buffer_length, error);
+ if (SR_SUCCESS != result) {
+ return result;
+ }
+ ASSERT(buffer_length_ >= new_buffer_length);
+ available = buffer_length_ - seek_position_;
+ }
+
+ if (bytes > available) {
+ bytes = available;
+ }
+ memcpy(&buffer_[seek_position_], buffer, bytes);
+ seek_position_ += bytes;
+ if (data_length_ < seek_position_) {
+ data_length_ = seek_position_;
+ }
+ if (bytes_written) {
+ *bytes_written = bytes;
+ }
+ return SR_SUCCESS;
+}
+
+void MemoryStreamBase::Close() {
+ // nothing to do
+}
+
+bool MemoryStreamBase::SetPosition(size_t position) {
+ if (position > data_length_)
+ return false;
+ seek_position_ = position;
+ return true;
+}
+
+bool MemoryStreamBase::GetPosition(size_t *position) const {
+ if (position)
+ *position = seek_position_;
+ return true;
+}
+
+bool MemoryStreamBase::GetSize(size_t *size) const {
+ if (size)
+ *size = data_length_;
+ return true;
+}
+
+bool MemoryStreamBase::GetAvailable(size_t *size) const {
+ if (size)
+ *size = data_length_ - seek_position_;
+ return true;
+}
+
+bool MemoryStreamBase::ReserveSize(size_t size) {
+ return (SR_SUCCESS == DoReserve(size, NULL));
+}
+
+StreamResult MemoryStreamBase::DoReserve(size_t size, int* error) {
+ return (buffer_length_ >= size) ? SR_SUCCESS : SR_EOS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+MemoryStream::MemoryStream()
+ : buffer_alloc_(NULL) {
+}
+
+MemoryStream::MemoryStream(const char* data)
+ : buffer_alloc_(NULL) {
+ SetData(data, strlen(data));
+}
+
+MemoryStream::MemoryStream(const void* data, size_t length)
+ : buffer_alloc_(NULL) {
+ SetData(data, length);
+}
+
+MemoryStream::~MemoryStream() {
+ delete [] buffer_alloc_;
+}
+
+void MemoryStream::SetData(const void* data, size_t length) {
+ data_length_ = buffer_length_ = length;
+ delete [] buffer_alloc_;
+ buffer_alloc_ = new char[buffer_length_ + kAlignment];
+ buffer_ = reinterpret_cast<char*>(ALIGNP(buffer_alloc_, kAlignment));
+ memcpy(buffer_, data, data_length_);
+ seek_position_ = 0;
+}
+
+StreamResult MemoryStream::DoReserve(size_t size, int* error) {
+ if (buffer_length_ >= size)
+ return SR_SUCCESS;
+
+ if (char* new_buffer_alloc = new char[size + kAlignment]) {
+ char* new_buffer = reinterpret_cast<char*>(
+ ALIGNP(new_buffer_alloc, kAlignment));
+ memcpy(new_buffer, buffer_, data_length_);
+ delete [] buffer_alloc_;
+ buffer_alloc_ = new_buffer_alloc;
+ buffer_ = new_buffer;
+ buffer_length_ = size;
+ return SR_SUCCESS;
+ }
+
+ if (error) {
+ *error = ENOMEM;
+ }
+ return SR_ERROR;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+ExternalMemoryStream::ExternalMemoryStream() {
+}
+
+ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) {
+ SetData(data, length);
+}
+
+ExternalMemoryStream::~ExternalMemoryStream() {
+}
+
+void ExternalMemoryStream::SetData(void* data, size_t length) {
+ data_length_ = buffer_length_ = length;
+ buffer_ = static_cast<char*>(data);
+ seek_position_ = 0;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// FifoBuffer
+///////////////////////////////////////////////////////////////////////////////
+
+FifoBuffer::FifoBuffer(size_t size)
+ : state_(SS_OPEN), buffer_(new char[size]), buffer_length_(size),
+ data_length_(0), read_position_(0), owner_(Thread::Current()) {
+ // all events are done on the owner_ thread
+}
+
+FifoBuffer::~FifoBuffer() {
+}
+
+bool FifoBuffer::GetBuffered(size_t* size) const {
+ CritScope cs(&crit_);
+ *size = data_length_;
+ return true;
+}
+
+bool FifoBuffer::SetCapacity(size_t size) {
+ CritScope cs(&crit_);
+ if (data_length_ > size) {
+ return false;
+ }
+
+ if (size != buffer_length_) {
+ char* buffer = new char[size];
+ const size_t copy = data_length_;
+ const size_t tail_copy = _min(copy, buffer_length_ - read_position_);
+ memcpy(buffer, &buffer_[read_position_], tail_copy);
+ memcpy(buffer + tail_copy, &buffer_[0], copy - tail_copy);
+ buffer_.reset(buffer);
+ read_position_ = 0;
+ buffer_length_ = size;
+ }
+ return true;
+}
+
+StreamState FifoBuffer::GetState() const {
+ return state_;
+}
+
+StreamResult FifoBuffer::Read(void* buffer, size_t bytes,
+ size_t* bytes_read, int* error) {
+ CritScope cs(&crit_);
+ const size_t available = data_length_;
+ if (0 == available) {
+ return (state_ != SS_CLOSED) ? SR_BLOCK : SR_EOS;
+ }
+
+ const bool was_writable = data_length_ < buffer_length_;
+ const size_t copy = _min(bytes, available);
+ const size_t tail_copy = _min(copy, buffer_length_ - read_position_);
+ char* const p = static_cast<char*>(buffer);
+ memcpy(p, &buffer_[read_position_], tail_copy);
+ memcpy(p + tail_copy, &buffer_[0], copy - tail_copy);
+ read_position_ = (read_position_ + copy) % buffer_length_;
+ data_length_ -= copy;
+ if (bytes_read) {
+ *bytes_read = copy;
+ }
+ // if we were full before, and now we're not, post an event
+ if (!was_writable && copy > 0) {
+ PostEvent(owner_, SE_WRITE, 0);
+ }
+
+ return SR_SUCCESS;
+}
+
+StreamResult FifoBuffer::Write(const void* buffer, size_t bytes,
+ size_t* bytes_written, int* error) {
+ CritScope cs(&crit_);
+ if (state_ == SS_CLOSED) {
+ return SR_EOS;
+ }
+
+ const size_t available = buffer_length_ - data_length_;
+ if (0 == available) {
+ return SR_BLOCK;
+ }
+
+ const bool was_readable = (data_length_ > 0);
+ const size_t write_position = (read_position_ + data_length_)
+ % buffer_length_;
+ const size_t copy = _min(bytes, available);
+ const size_t tail_copy = _min(copy, buffer_length_ - write_position);
+ const char* const p = static_cast<const char*>(buffer);
+ memcpy(&buffer_[write_position], p, tail_copy);
+ memcpy(&buffer_[0], p + tail_copy, copy - tail_copy);
+ data_length_ += copy;
+ if (bytes_written) {
+ *bytes_written = copy;
+ }
+ // if we didn't have any data to read before, and now we do, post an event
+ if (!was_readable && copy > 0) {
+ PostEvent(owner_, SE_READ, 0);
+ }
+
+ return SR_SUCCESS;
+}
+
+void FifoBuffer::Close() {
+ CritScope cs(&crit_);
+ state_ = SS_CLOSED;
+}
+
+const void* FifoBuffer::GetReadData(size_t* size) {
+ CritScope cs(&crit_);
+ *size = (read_position_ + data_length_ <= buffer_length_) ?
+ data_length_ : buffer_length_ - read_position_;
+ return &buffer_[read_position_];
+}
+
+void FifoBuffer::ConsumeReadData(size_t size) {
+ CritScope cs(&crit_);
+ ASSERT(size <= data_length_);
+ const bool was_writable = data_length_ < buffer_length_;
+ read_position_ = (read_position_ + size) % buffer_length_;
+ data_length_ -= size;
+ if (!was_writable && size > 0) {
+ PostEvent(owner_, SE_WRITE, 0);
+ }
+}
+
+void* FifoBuffer::GetWriteBuffer(size_t* size) {
+ CritScope cs(&crit_);
+ if (state_ == SS_CLOSED) {
+ return NULL;
+ }
+
+ // if empty, reset the write position to the beginning, so we can get
+ // the biggest possible block
+ if (data_length_ == 0) {
+ read_position_ = 0;
+ }
+
+ const size_t write_position = (read_position_ + data_length_)
+ % buffer_length_;
+ *size = (write_position >= read_position_) ?
+ buffer_length_ - write_position : read_position_ - write_position;
+ return &buffer_[write_position];
+}
+
+void FifoBuffer::ConsumeWriteBuffer(size_t size) {
+ CritScope cs(&crit_);
+ ASSERT(size <= buffer_length_ - data_length_);
+ const bool was_readable = (data_length_ > 0);
+ data_length_ += size;
+ if (!was_readable && size > 0) {
+ PostEvent(owner_, SE_READ, 0);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// LoggingAdapter
+///////////////////////////////////////////////////////////////////////////////
+
+LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+ const std::string& label, bool hex_mode)
+: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode)
+{
+ set_label(label);
+}
+
+void LoggingAdapter::set_label(const std::string& label) {
+ label_.assign("[");
+ label_.append(label);
+ label_.append("]");
+}
+
+StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ size_t local_read; if (!read) read = &local_read;
+ StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read,
+ error);
+ if (result == SR_SUCCESS) {
+ LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_);
+ }
+ return result;
+}
+
+StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ size_t local_written; if (!written) written = &local_written;
+ StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
+ error);
+ if (result == SR_SUCCESS) {
+ LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_,
+ &lms_);
+ }
+ return result;
+}
+
+void LoggingAdapter::Close() {
+ LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+ LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+ LOG_V(level_) << label_ << " Closed locally";
+ StreamAdapterInterface::Close();
+}
+
+void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) {
+ if (events & SE_OPEN) {
+ LOG_V(level_) << label_ << " Open";
+ } else if (events & SE_CLOSE) {
+ LogMultiline(level_, label_.c_str(), false, NULL, 0, hex_mode_, &lms_);
+ LogMultiline(level_, label_.c_str(), true, NULL, 0, hex_mode_, &lms_);
+ LOG_V(level_) << label_ << " Closed with error: " << err;
+ }
+ StreamAdapterInterface::OnEvent(stream, events, err);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+StringStream::StringStream(std::string& str)
+: str_(str), read_pos_(0), read_only_(false)
+{
+}
+
+StringStream::StringStream(const std::string& str)
+: str_(const_cast<std::string&>(str)), read_pos_(0), read_only_(true)
+{
+}
+
+StreamState StringStream::GetState() const {
+ return SS_OPEN;
+}
+
+StreamResult StringStream::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ size_t available = _min(buffer_len, str_.size() - read_pos_);
+ if (!available)
+ return SR_EOS;
+ memcpy(buffer, str_.data() + read_pos_, available);
+ read_pos_ += available;
+ if (read)
+ *read = available;
+ return SR_SUCCESS;
+}
+
+StreamResult StringStream::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ if (read_only_) {
+ if (error) {
+ *error = -1;
+ }
+ return SR_ERROR;
+ }
+ str_.append(static_cast<const char*>(data),
+ static_cast<const char*>(data) + data_len);
+ if (written)
+ *written = data_len;
+ return SR_SUCCESS;
+}
+
+void StringStream::Close() {
+}
+
+bool StringStream::SetPosition(size_t position) {
+ if (position > str_.size())
+ return false;
+ read_pos_ = position;
+ return true;
+}
+
+bool StringStream::GetPosition(size_t* position) const {
+ if (position)
+ *position = read_pos_;
+ return true;
+}
+
+bool StringStream::GetSize(size_t* size) const {
+ if (size)
+ *size = str_.size();
+ return true;
+}
+
+bool StringStream::GetAvailable(size_t* size) const {
+ if (size)
+ *size = str_.size() - read_pos_;
+ return true;
+}
+
+bool StringStream::ReserveSize(size_t size) {
+ if (read_only_)
+ return false;
+ str_.reserve(size);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference
+///////////////////////////////////////////////////////////////////////////////
+
+StreamReference::StreamReference(StreamInterface* stream)
+ : StreamAdapterInterface(stream, false) {
+ // owner set to false so the destructor does not free the stream.
+ stream_ref_count_ = new StreamRefCount(stream);
+}
+
+StreamInterface* StreamReference::NewReference() {
+ stream_ref_count_->AddReference();
+ return new StreamReference(stream_ref_count_, stream());
+}
+
+StreamReference::~StreamReference() {
+ stream_ref_count_->Release();
+}
+
+StreamReference::StreamReference(StreamRefCount* stream_ref_count,
+ StreamInterface* stream)
+ : StreamAdapterInterface(stream, false),
+ stream_ref_count_(stream_ref_count) {
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+StreamResult Flow(StreamInterface* source,
+ char* buffer, size_t buffer_len,
+ StreamInterface* sink,
+ size_t* data_len /* = NULL */) {
+ ASSERT(buffer_len > 0);
+
+ StreamResult result;
+ size_t count, read_pos, write_pos;
+ if (data_len) {
+ read_pos = *data_len;
+ } else {
+ read_pos = 0;
+ }
+
+ bool end_of_stream = false;
+ do {
+ // Read until buffer is full, end of stream, or error
+ while (!end_of_stream && (read_pos < buffer_len)) {
+ result = source->Read(buffer + read_pos, buffer_len - read_pos,
+ &count, NULL);
+ if (result == SR_EOS) {
+ end_of_stream = true;
+ } else if (result != SR_SUCCESS) {
+ if (data_len) {
+ *data_len = read_pos;
+ }
+ return result;
+ } else {
+ read_pos += count;
+ }
+ }
+
+ // Write until buffer is empty, or error (including end of stream)
+ write_pos = 0;
+ while (write_pos < read_pos) {
+ result = sink->Write(buffer + write_pos, read_pos - write_pos,
+ &count, NULL);
+ if (result != SR_SUCCESS) {
+ if (data_len) {
+ *data_len = read_pos - write_pos;
+ if (write_pos > 0) {
+ memmove(buffer, buffer + write_pos, *data_len);
+ }
+ }
+ return result;
+ }
+ write_pos += count;
+ }
+
+ read_pos = 0;
+ } while (!end_of_stream);
+
+ if (data_len) {
+ *data_len = 0;
+ }
+ return SR_SUCCESS;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/stream.h b/talk/base/stream.h
new file mode 100644
index 0000000..a8d399d
--- /dev/null
+++ b/talk/base/stream.h
@@ -0,0 +1,720 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_STREAM_H__
+#define TALK_BASE_STREAM_H__
+
+#include "talk/base/basictypes.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/logging.h"
+#include "talk/base/messagehandler.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamInterface is a generic asynchronous stream interface, supporting read,
+// write, and close operations, and asynchronous signalling of state changes.
+// The interface is designed with file, memory, and socket implementations in
+// mind. Some implementations offer extended operations, such as seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+// The following enumerations are declared outside of the StreamInterface
+// class for brevity in use.
+
+// The SS_OPENING state indicates that the stream will signal open or closed
+// in the future.
+enum StreamState { SS_CLOSED, SS_OPENING, SS_OPEN };
+
+// Stream read/write methods return this value to indicate various success
+// and failure conditions described below.
+enum StreamResult { SR_ERROR, SR_SUCCESS, SR_BLOCK, SR_EOS };
+
+// StreamEvents are used to asynchronously signal state transitionss. The flags
+// may be combined.
+// SE_OPEN: The stream has transitioned to the SS_OPEN state
+// SE_CLOSE: The stream has transitioned to the SS_CLOSED state
+// SE_READ: Data is available, so Read is likely to not return SR_BLOCK
+// SE_WRITE: Data can be written, so Write is likely to not return SR_BLOCK
+enum StreamEvent { SE_OPEN = 1, SE_READ = 2, SE_WRITE = 4, SE_CLOSE = 8 };
+
+class Thread;
+
+class StreamInterface : public MessageHandler {
+ public:
+ virtual ~StreamInterface();
+
+ virtual StreamState GetState() const = 0;
+
+ // Read attempts to fill buffer of size buffer_len. Write attempts to send
+ // data_len bytes stored in data. The variables read and write are set only
+ // on SR_SUCCESS (see below). Likewise, error is only set on SR_ERROR.
+ // Read and Write return a value indicating:
+ // SR_ERROR: an error occurred, which is returned in a non-null error
+ // argument. Interpretation of the error requires knowledge of the
+ // stream's concrete type, which limits its usefulness.
+ // SR_SUCCESS: some number of bytes were successfully written, which is
+ // returned in a non-null read/write argument.
+ // SR_BLOCK: the stream is in non-blocking mode, and the operation would
+ // block, or the stream is in SS_OPENING state.
+ // SR_EOS: the end-of-stream has been reached, or the stream is in the
+ // SS_CLOSED state.
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) = 0;
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error) = 0;
+ // Attempt to transition to the SS_CLOSED state. SE_CLOSE will not be
+ // signalled as a result of this call.
+ virtual void Close() = 0;
+
+ // Streams may signal one or more StreamEvents to indicate state changes.
+ // The first argument identifies the stream on which the state change occured.
+ // The second argument is a bit-wise combination of StreamEvents.
+ // If SE_CLOSE is signalled, then the third argument is the associated error
+ // code. Otherwise, the value is undefined.
+ // Note: Not all streams will support asynchronous event signalling. However,
+ // SS_OPENING and SR_BLOCK returned from stream member functions imply that
+ // certain events will be raised in the future.
+ sigslot::signal3<StreamInterface*, int, int> SignalEvent;
+
+ // Like calling SignalEvent, but posts a message to the specified thread,
+ // which will call SignalEvent. This helps unroll the stack and prevent
+ // re-entrancy.
+ void PostEvent(Thread* t, int events, int err);
+ // Like the aforementioned method, but posts to the current thread.
+ void PostEvent(int events, int err);
+
+ //
+ // OPTIONAL OPERATIONS
+ //
+ // Not all implementations will support the following operations. In general,
+ // a stream will only support an operation if it reasonably efficient to do
+ // so. For example, while a socket could buffer incoming data to support
+ // seeking, it will not do so. Instead, a buffering stream adapter should
+ // be used.
+ //
+ // Even though several of these operations are related, you should
+ // always use whichever operation is most relevant. For example, you may
+ // be tempted to use GetSize() and GetPosition() to deduce the result of
+ // GetAvailable(). However, a stream which is read-once may support the
+ // latter operation but not the former.
+ //
+
+ // The following four methods are used to avoid coping data multiple times.
+
+ // GetReadData returns a pointer to a buffer which is owned by the stream.
+ // The buffer contains data_len bytes. NULL is returned if no data is
+ // available, or if the method fails. If the caller processes the data, it
+ // must call ConsumeReadData with the number of processed bytes. GetReadData
+ // does not require a matching call to ConsumeReadData if the data is not
+ // processed. Read and ConsumeReadData invalidate the buffer returned by
+ // GetReadData.
+ virtual const void* GetReadData(size_t* data_len) { return NULL; }
+ virtual void ConsumeReadData(size_t used) {}
+
+ // GetWriteBuffer returns a pointer to a buffer which is owned by the stream.
+ // The buffer has a capacity of buf_len bytes. NULL is returned if there is
+ // no buffer available, or if the method fails. The call may write data to
+ // the buffer, and then call ConsumeWriteBuffer with the number of bytes
+ // written. GetWriteBuffer does not require a matching call to
+ // ConsumeWriteData if no data is written. Write, ForceWrite, and
+ // ConsumeWriteData invalidate the buffer returned by GetWriteBuffer.
+ // TODO: Allow the caller to specify a minimum buffer size. If the specified
+ // amount of buffer is not yet available, return NULL and Signal SE_WRITE
+ // when it is available. If the requested amount is too large, return an
+ // error.
+ virtual void* GetWriteBuffer(size_t* buf_len) { return NULL; }
+ virtual void ConsumeWriteBuffer(size_t used) {}
+
+ // Write data_len bytes found in data, circumventing any throttling which
+ // would could cause SR_BLOCK to be returned. Returns true if all the data
+ // was written. Otherwise, the method is unsupported, or an unrecoverable
+ // error occurred, and the error value is set. This method should be used
+ // sparingly to write critical data which should not be throttled. A stream
+ // which cannot circumvent its blocking constraints should not implement this
+ // method.
+ // NOTE: This interface is being considered experimentally at the moment. It
+ // would be used by JUDP and BandwidthStream as a way to circumvent certain
+ // soft limits in writing.
+ //virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+ // if (error) *error = -1;
+ // return false;
+ //}
+
+ // Seek to a byte offset from the beginning of the stream. Returns false if
+ // the stream does not support seeking, or cannot seek to the specified
+ // position.
+ virtual bool SetPosition(size_t position) { return false; }
+
+ // Get the byte offset of the current position from the start of the stream.
+ // Returns false if the position is not known.
+ virtual bool GetPosition(size_t* position) const { return false; }
+
+ // Get the byte length of the entire stream. Returns false if the length
+ // is not known.
+ virtual bool GetSize(size_t* size) const { return false; }
+
+ // Return the number of Read()-able bytes remaining before end-of-stream.
+ // Returns false if not known.
+ virtual bool GetAvailable(size_t* size) const { return false; }
+
+ // Return the number of Write()-able bytes remaining before end-of-stream.
+ // Returns false if not known.
+ virtual bool GetWriteRemaining(size_t* size) const { return false; }
+
+ // Communicates the amount of data which will be written to the stream. The
+ // stream may choose to preallocate memory to accomodate this data. The
+ // stream may return false to indicate that there is not enough room (ie,
+ // Write will return SR_EOS/SR_ERROR at some point). Note that calling this
+ // function should not affect the existing state of data in the stream.
+ virtual bool ReserveSize(size_t size) { return true; }
+
+ //
+ // CONVENIENCE METHODS
+ //
+ // These methods are implemented in terms of other methods, for convenience.
+ //
+
+ // Seek to the start of the stream.
+ inline bool Rewind() { return SetPosition(0); }
+
+ // WriteAll is a helper function which repeatedly calls Write until all the
+ // data is written, or something other than SR_SUCCESS is returned. Note that
+ // unlike Write, the argument 'written' is always set, and may be non-zero
+ // on results other than SR_SUCCESS. The remaining arguments have the
+ // same semantics as Write.
+ StreamResult WriteAll(const void* data, size_t data_len,
+ size_t* written, int* error);
+
+ // Similar to ReadAll. Calls Read until buffer_len bytes have been read, or
+ // until a non-SR_SUCCESS result is returned. 'read' is always set.
+ StreamResult ReadAll(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+
+ // ReadLine is a helper function which repeatedly calls Read until it hits
+ // the end-of-line character, or something other than SR_SUCCESS.
+ // TODO: this is too inefficient to keep here. Break this out into a buffered
+ // readline object or adapter
+ StreamResult ReadLine(std::string *line);
+
+ protected:
+ StreamInterface();
+
+ // MessageHandler Interface
+ virtual void OnMessage(Message* msg);
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(StreamInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamAdapterInterface is a convenient base-class for adapting a stream.
+// By default, all operations are pass-through. Override the methods that you
+// require adaptation. Streams should really be upgraded to reference-counted.
+// In the meantime, use the owned flag to indicate whether the adapter should
+// own the adapted stream.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamAdapterInterface : public StreamInterface,
+ public sigslot::has_slots<> {
+ public:
+ explicit StreamAdapterInterface(StreamInterface* stream, bool owned = true);
+
+ // Core Stream Interface
+ virtual StreamState GetState() const {
+ return stream_->GetState();
+ }
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ return stream_->Read(buffer, buffer_len, read, error);
+ }
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ return stream_->Write(data, data_len, written, error);
+ }
+ virtual void Close() {
+ stream_->Close();
+ }
+
+ // Optional Stream Interface
+ /* Note: Many stream adapters were implemented prior to this Read/Write
+ interface. Therefore, a simple pass through of data in those cases may
+ be broken. At a later time, we should do a once-over pass of all
+ adapters, and make them compliant with these interfaces, after which this
+ code can be uncommented.
+ virtual const void* GetReadData(size_t* data_len) {
+ return stream_->GetReadData(data_len);
+ }
+ virtual void ConsumeReadData(size_t used) {
+ stream_->ConsumeReadData(used);
+ }
+
+ virtual void* GetWriteBuffer(size_t* buf_len) {
+ return stream_->GetWriteBuffer(buf_len);
+ }
+ virtual void ConsumeWriteBuffer(size_t used) {
+ stream_->ConsumeWriteBuffer(used);
+ }
+ */
+
+ /* Note: This interface is currently undergoing evaluation.
+ virtual bool ForceWrite(const void* data, size_t data_len, int* error) {
+ return stream_->ForceWrite(data, data_len, error);
+ }
+ */
+
+ virtual bool SetPosition(size_t position) {
+ return stream_->SetPosition(position);
+ }
+ virtual bool GetPosition(size_t* position) const {
+ return stream_->GetPosition(position);
+ }
+ virtual bool GetSize(size_t* size) const {
+ return stream_->GetSize(size);
+ }
+ virtual bool GetAvailable(size_t* size) const {
+ return stream_->GetAvailable(size);
+ }
+ virtual bool GetWriteRemaining(size_t* size) const {
+ return stream_->GetWriteRemaining(size);
+ }
+ virtual bool ReserveSize(size_t size) {
+ return stream_->ReserveSize(size);
+ }
+
+ void Attach(StreamInterface* stream, bool owned = true);
+ StreamInterface* Detach();
+
+ protected:
+ virtual ~StreamAdapterInterface();
+
+ // Note that the adapter presents itself as the origin of the stream events,
+ // since users of the adapter may not recognize the adapted object.
+ virtual void OnEvent(StreamInterface* stream, int events, int err) {
+ SignalEvent(this, events, err);
+ }
+ StreamInterface* stream() { return stream_; }
+
+ private:
+ StreamInterface* stream_;
+ bool owned_;
+ DISALLOW_EVIL_CONSTRUCTORS(StreamAdapterInterface);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamTap is a non-modifying, pass-through adapter, which copies all data
+// in either direction to the tap. Note that errors or blocking on writing to
+// the tap will prevent further tap writes from occurring.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamTap : public StreamAdapterInterface {
+ public:
+ explicit StreamTap(StreamInterface* stream, StreamInterface* tap);
+
+ void AttachTap(StreamInterface* tap);
+ StreamInterface* DetachTap();
+ StreamResult GetTapResult(int* error);
+
+ // StreamAdapterInterface Interface
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+
+ private:
+ scoped_ptr<StreamInterface> tap_;
+ StreamResult tap_result_;
+ int tap_error_;
+ DISALLOW_EVIL_CONSTRUCTORS(StreamTap);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamSegment adapts a read stream, to expose a subset of the adapted
+// stream's data. This is useful for cases where a stream contains multiple
+// documents concatenated together. StreamSegment can expose a subset of
+// the data as an independent stream, including support for rewinding and
+// seeking.
+///////////////////////////////////////////////////////////////////////////////
+
+class StreamSegment : public StreamAdapterInterface {
+ public:
+ // The current position of the adapted stream becomes the beginning of the
+ // segment. If a length is specified, it bounds the length of the segment.
+ explicit StreamSegment(StreamInterface* stream);
+ explicit StreamSegment(StreamInterface* stream, size_t length);
+
+ // StreamAdapterInterface Interface
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual bool SetPosition(size_t position);
+ virtual bool GetPosition(size_t* position) const;
+ virtual bool GetSize(size_t* size) const;
+ virtual bool GetAvailable(size_t* size) const;
+
+ private:
+ size_t start_, pos_, length_;
+ DISALLOW_EVIL_CONSTRUCTORS(StreamSegment);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// NullStream gives errors on read, and silently discards all written data.
+///////////////////////////////////////////////////////////////////////////////
+
+class NullStream : public StreamInterface {
+ public:
+ NullStream();
+ virtual ~NullStream();
+
+ // StreamInterface Interface
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// FileStream is a simple implementation of a StreamInterface, which does not
+// support asynchronous notification.
+///////////////////////////////////////////////////////////////////////////////
+
+class FileStream : public StreamInterface {
+ public:
+ FileStream();
+ virtual ~FileStream();
+
+ // The semantics of filename and mode are the same as stdio's fopen
+ virtual bool Open(const std::string& filename, const char* mode);
+ virtual bool OpenShare(const std::string& filename, const char* mode,
+ int shflag);
+
+ // By default, reads and writes are buffered for efficiency. Disabling
+ // buffering causes writes to block until the bytes on disk are updated.
+ virtual bool DisableBuffering();
+
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+ virtual bool SetPosition(size_t position);
+ virtual bool GetPosition(size_t* position) const;
+ virtual bool GetSize(size_t* size) const;
+ virtual bool GetAvailable(size_t* size) const;
+ virtual bool ReserveSize(size_t size);
+
+ bool Flush();
+
+#if defined(POSIX)
+ // Tries to aquire an exclusive lock on the file.
+ // Use OpenShare(...) on win32 to get similar functionality.
+ bool TryLock();
+ bool Unlock();
+#endif
+
+ // Note: Deprecated in favor of Filesystem::GetFileSize().
+ static bool GetSize(const std::string& filename, size_t* size);
+
+ protected:
+ virtual void DoClose();
+
+ FILE* file_;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(FileStream);
+};
+
+#ifdef POSIX
+// A FileStream that is actually not a file, but the output or input of a
+// sub-command. See "man 3 popen" for documentation of the underlying OS popen()
+// function.
+class POpenStream : public FileStream {
+ public:
+ POpenStream() : wait_status_(-1) {}
+ virtual ~POpenStream();
+
+ virtual bool Open(const std::string& subcommand, const char* mode);
+ // Same as Open(). shflag is ignored.
+ virtual bool OpenShare(const std::string& subcommand, const char* mode,
+ int shflag);
+
+ // Returns the wait status from the last Close() of an Open()'ed stream, or
+ // -1 if no Open()+Close() has been done on this object. Meaning of the number
+ // is documented in "man 2 wait".
+ int GetWaitStatus() const { return wait_status_; }
+
+ protected:
+ virtual void DoClose();
+
+ private:
+ int wait_status_;
+};
+#endif // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// MemoryStream is a simple implementation of a StreamInterface over in-memory
+// data. Data is read and written at the current seek position. Reads return
+// end-of-stream when they reach the end of data. Writes actually extend the
+// end of data mark.
+///////////////////////////////////////////////////////////////////////////////
+
+class MemoryStreamBase : public StreamInterface {
+ public:
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t bytes, size_t* bytes_read,
+ int* error);
+ virtual StreamResult Write(const void* buffer, size_t bytes,
+ size_t* bytes_written, int* error);
+ virtual void Close();
+ virtual bool SetPosition(size_t position);
+ virtual bool GetPosition(size_t* position) const;
+ virtual bool GetSize(size_t* size) const;
+ virtual bool GetAvailable(size_t* size) const;
+ virtual bool ReserveSize(size_t size);
+
+ char* GetBuffer() { return buffer_; }
+ const char* GetBuffer() const { return buffer_; }
+
+ protected:
+ MemoryStreamBase();
+
+ virtual StreamResult DoReserve(size_t size, int* error);
+
+ // Invariant: 0 <= seek_position <= data_length_ <= buffer_length_
+ char* buffer_;
+ size_t buffer_length_;
+ size_t data_length_;
+ size_t seek_position_;
+
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(MemoryStreamBase);
+};
+
+// MemoryStream dynamically resizes to accomodate written data.
+
+class MemoryStream : public MemoryStreamBase {
+ public:
+ MemoryStream();
+ explicit MemoryStream(const char* data); // Calls SetData(data, strlen(data))
+ MemoryStream(const void* data, size_t length); // Calls SetData(data, length)
+ virtual ~MemoryStream();
+
+ void SetData(const void* data, size_t length);
+
+ protected:
+ virtual StreamResult DoReserve(size_t size, int* error);
+ // Memory Streams are aligned for efficiency.
+ static const int kAlignment = 16;
+ char* buffer_alloc_;
+};
+
+// ExternalMemoryStream adapts an external memory buffer, so writes which would
+// extend past the end of the buffer will return end-of-stream.
+
+class ExternalMemoryStream : public MemoryStreamBase {
+ public:
+ ExternalMemoryStream();
+ ExternalMemoryStream(void* data, size_t length);
+ virtual ~ExternalMemoryStream();
+
+ void SetData(void* data, size_t length);
+};
+
+// FifoBuffer allows for efficient, thread-safe buffering of data between
+// writer and reader. As the data can wrap around the end of the buffer,
+// MemoryStreamBase can't help us here.
+
+class FifoBuffer : public StreamInterface {
+ public:
+ // Creates a FIFO buffer with the specified capacity.
+ explicit FifoBuffer(size_t length);
+ virtual ~FifoBuffer();
+ // Gets the amount of data currently readable from the buffer.
+ bool GetBuffered(size_t* data_len) const;
+ // Resizes the buffer to the specified capacity. Fails if data_length_ > size
+ bool SetCapacity(size_t length);
+
+ // StreamInterface methods
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t bytes,
+ size_t* bytes_read, int* error);
+ virtual StreamResult Write(const void* buffer, size_t bytes,
+ size_t* bytes_written, int* error);
+ virtual void Close();
+ virtual const void* GetReadData(size_t* data_len);
+ virtual void ConsumeReadData(size_t used);
+ virtual void* GetWriteBuffer(size_t *buf_len);
+ virtual void ConsumeWriteBuffer(size_t used);
+
+ private:
+ StreamState state_; // keeps the opened/closed state of the stream
+ scoped_array<char> buffer_; // the allocated buffer
+ size_t buffer_length_; // size of the allocated buffer
+ size_t data_length_; // amount of readable data in the buffer
+ size_t read_position_; // offset to the readable data
+ Thread* owner_; // stream callbacks are dispatched on this thread
+ mutable CriticalSection crit_; // object lock
+ DISALLOW_EVIL_CONSTRUCTORS(FifoBuffer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class LoggingAdapter : public StreamAdapterInterface {
+ public:
+ LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
+ const std::string& label, bool hex_mode = false);
+
+ void set_label(const std::string& label);
+
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+
+ protected:
+ virtual void OnEvent(StreamInterface* stream, int events, int err);
+
+ private:
+ LoggingSeverity level_;
+ std::string label_;
+ bool hex_mode_;
+ LogMultilineState lms_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(LoggingAdapter);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StringStream - Reads/Writes to an external std::string
+///////////////////////////////////////////////////////////////////////////////
+
+class StringStream : public StreamInterface {
+ public:
+ explicit StringStream(std::string& str);
+ explicit StringStream(const std::string& str);
+
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+ virtual bool SetPosition(size_t position);
+ virtual bool GetPosition(size_t* position) const;
+ virtual bool GetSize(size_t* size) const;
+ virtual bool GetAvailable(size_t* size) const;
+ virtual bool ReserveSize(size_t size);
+
+ private:
+ std::string& str_;
+ size_t read_pos_;
+ bool read_only_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// StreamReference - A reference counting stream adapter
+///////////////////////////////////////////////////////////////////////////////
+
+// Keep in mind that the streams and adapters defined in this file are
+// not thread-safe, so this has limited uses.
+
+// A StreamRefCount holds the reference count and a pointer to the
+// wrapped stream. It deletes the wrapped stream when there are no
+// more references. We can then have multiple StreamReference
+// instances pointing to one StreamRefCount, all wrapping the same
+// stream.
+
+class StreamReference : public StreamAdapterInterface {
+ class StreamRefCount;
+ public:
+ // Constructor for the first reference to a stream
+ // Note: get more references through NewReference(). Use this
+ // constructor only once on a given stream.
+ explicit StreamReference(StreamInterface* stream);
+ StreamInterface* GetStream() { return stream(); }
+ StreamInterface* NewReference();
+ virtual ~StreamReference();
+
+ private:
+ class StreamRefCount {
+ public:
+ explicit StreamRefCount(StreamInterface* stream)
+ : stream_(stream), ref_count_(1) {
+ }
+ void AddReference() {
+ CritScope lock(&cs_);
+ ++ref_count_;
+ }
+ void Release() {
+ int ref_count;
+ { // Atomic ops would have been a better fit here.
+ CritScope lock(&cs_);
+ ref_count = --ref_count_;
+ }
+ if (ref_count == 0) {
+ delete stream_;
+ delete this;
+ }
+ }
+ private:
+ StreamInterface* stream_;
+ int ref_count_;
+ CriticalSection cs_;
+ DISALLOW_EVIL_CONSTRUCTORS(StreamRefCount);
+ };
+
+ // Constructor for adding references
+ explicit StreamReference(StreamRefCount* stream_ref_count,
+ StreamInterface* stream);
+
+ StreamRefCount* stream_ref_count_;
+ DISALLOW_EVIL_CONSTRUCTORS(StreamReference);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+// Flow attempts to move bytes from source to sink via buffer of size
+// buffer_len. The function returns SR_SUCCESS when source reaches
+// end-of-stream (returns SR_EOS), and all the data has been written successful
+// to sink. Alternately, if source returns SR_BLOCK or SR_ERROR, or if sink
+// returns SR_BLOCK, SR_ERROR, or SR_EOS, then the function immediately returns
+// with the unexpected StreamResult value.
+// data_len is the length of the valid data in buffer. in case of error
+// this is the data that read from source but can't move to destination.
+// as a pass in parameter, it indicates data in buffer that should move to sink
+StreamResult Flow(StreamInterface* source,
+ char* buffer, size_t buffer_len,
+ StreamInterface* sink, size_t* data_len = NULL);
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_STREAM_H__
diff --git a/talk/base/stringdigest.cc b/talk/base/stringdigest.cc
new file mode 100644
index 0000000..1f98124
--- /dev/null
+++ b/talk/base/stringdigest.cc
@@ -0,0 +1,49 @@
+/*
+ * 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 "talk/base/md5.h"
+#include "talk/base/stringdigest.h"
+#include "talk/base/stringencode.h"
+
+namespace talk_base {
+
+std::string MD5(const std::string& data) {
+ MD5_CTX ctx;
+ MD5Init(&ctx);
+ MD5Update(&ctx, const_cast<unsigned char *>(reinterpret_cast<const unsigned char *>(data.data())), static_cast<unsigned int>(data.size()));
+ unsigned char digest[16];
+ MD5Final(digest, &ctx);
+ std::string hex_digest;
+ for (int i=0; i<16; ++i) {
+ hex_digest += hex_encode(digest[i] >> 4);
+ hex_digest += hex_encode(digest[i] & 0xf);
+ }
+ return hex_digest;
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringdigest.h b/talk/base/stringdigest.h
new file mode 100644
index 0000000..d75d845
--- /dev/null
+++ b/talk/base/stringdigest.h
@@ -0,0 +1,47 @@
+/*
+ * 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_STRINGDIGEST_H__
+#define TALK_BASE_STRINGDIGEST_H__
+
+#include <string>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Message Digest Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Compute the MD5 message digest of data, and return it in
+std::string MD5(const std::string& data);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_STRINGDIGEST_H__
diff --git a/talk/base/stringencode.cc b/talk/base/stringencode.cc
new file mode 100644
index 0000000..7433bdd
--- /dev/null
+++ b/talk/base/stringencode.cc
@@ -0,0 +1,581 @@
+/*
+ * 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/base/stringencode.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+namespace talk_base {
+
+/////////////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+/////////////////////////////////////////////////////////////////////////////
+
+static const char HEX[] = "0123456789abcdef";
+
+char hex_encode(unsigned char val) {
+ ASSERT(val < 16);
+ return (val < 16) ? HEX[val] : '!';
+}
+
+bool hex_decode(char ch, unsigned char* val) {
+ if ((ch >= '0') && (ch <= '9')) {
+ *val = ch - '0';
+ } else if ((ch >= 'A') && (ch <= 'Z')) {
+ *val = (ch - 'A') + 10;
+ } else if ((ch >= 'a') && (ch <= 'z')) {
+ *val = (ch - 'a') + 10;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+size_t escape(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ const char * illegal, char escape) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ char ch = source[srcpos++];
+ if ((ch == escape) || ::strchr(illegal, ch)) {
+ if (bufpos + 2 >= buflen)
+ break;
+ buffer[bufpos++] = escape;
+ }
+ buffer[bufpos++] = ch;
+ }
+
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t unescape(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ char escape) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ char ch = source[srcpos++];
+ if ((ch == escape) && (srcpos < srclen)) {
+ ch = source[srcpos++];
+ }
+ buffer[bufpos++] = ch;
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ const char * illegal, char escape) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ char ch = source[srcpos++];
+ if ((ch != escape) && !::strchr(illegal, ch)) {
+ buffer[bufpos++] = ch;
+ } else if (bufpos + 3 >= buflen) {
+ break;
+ } else {
+ buffer[bufpos+0] = escape;
+ buffer[bufpos+1] = hex_encode((static_cast<unsigned char>(ch) >> 4) & 0xF);
+ buffer[bufpos+2] = hex_encode((static_cast<unsigned char>(ch) ) & 0xF);
+ bufpos += 3;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ char escape) {
+ if (buflen <= 0)
+ return 0;
+
+ unsigned char h1, h2;
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ char ch = source[srcpos++];
+ if ((ch == escape)
+ && (srcpos + 1 < srclen)
+ && hex_decode(source[srcpos], &h1)
+ && hex_decode(source[srcpos+1], &h2)) {
+ buffer[bufpos++] = (h1 << 4) | h2;
+ srcpos += 2;
+ } else {
+ buffer[bufpos++] = ch;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+const char* unsafe_filename_characters() {
+ // It might be better to have a single specification which is the union of
+ // all operating systems, unless one system is overly restrictive.
+#ifdef WIN32
+ return "\\/:*?\"<>|";
+#else // !WIN32
+ // TODO
+ ASSERT(false);
+ return "";
+#endif // !WIN23
+}
+
+const unsigned char URL_UNSAFE = 0x1; // 0-33 "#$%&+,/:;<=>?@[\]^`{|} 127
+const unsigned char XML_UNSAFE = 0x2; // "&'<>
+const unsigned char HTML_UNSAFE = 0x2; // "&'<>
+
+// ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 6 5 7 8 9 : ; < = > ?
+//@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
+//` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~
+
+const unsigned char ASCII_CLASS[128] = {
+ 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
+ 1,0,3,1,1,1,3,2,0,0,0,1,1,0,0,1,0,0,0,0,0,0,0,0,0,0,1,1,3,1,3,1,
+ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,
+ 1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,1,
+};
+
+size_t url_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ if (NULL == buffer)
+ return srclen * 3 + 1;
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ unsigned char ch = source[srcpos++];
+ if ((ch < 128) && (ASCII_CLASS[ch] & URL_UNSAFE)) {
+ if (bufpos + 3 >= buflen) {
+ break;
+ }
+ buffer[bufpos+0] = '%';
+ buffer[bufpos+1] = hex_encode((ch >> 4) & 0xF);
+ buffer[bufpos+2] = hex_encode((ch ) & 0xF);
+ bufpos += 3;
+ } else {
+ buffer[bufpos++] = ch;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t url_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ if (NULL == buffer)
+ return srclen + 1;
+ if (buflen <= 0)
+ return 0;
+
+ unsigned char h1, h2;
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ unsigned char ch = source[srcpos++];
+ if (ch == '+') {
+ buffer[bufpos++] = ' ';
+ } else if ((ch == '%')
+ && (srcpos + 1 < srclen)
+ && hex_decode(source[srcpos], &h1)
+ && hex_decode(source[srcpos+1], &h2))
+ {
+ buffer[bufpos++] = (h1 << 4) | h2;
+ srcpos += 2;
+ } else {
+ buffer[bufpos++] = ch;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value) {
+ const unsigned char* s = reinterpret_cast<const unsigned char*>(source);
+ if ((s[0] & 0x80) == 0x00) { // Check s[0] == 0xxxxxxx
+ *value = s[0];
+ return 1;
+ }
+ if ((srclen < 2) || ((s[1] & 0xC0) != 0x80)) { // Check s[1] != 10xxxxxx
+ return 0;
+ }
+ // Accumulate the trailer byte values in value16, and combine it with the
+ // relevant bits from s[0], once we've determined the sequence length.
+ unsigned long value16 = (s[1] & 0x3F);
+ if ((s[0] & 0xE0) == 0xC0) { // Check s[0] == 110xxxxx
+ *value = ((s[0] & 0x1F) << 6) | value16;
+ return 2;
+ }
+ if ((srclen < 3) || ((s[2] & 0xC0) != 0x80)) { // Check s[2] != 10xxxxxx
+ return 0;
+ }
+ value16 = (value16 << 6) | (s[2] & 0x3F);
+ if ((s[0] & 0xF0) == 0xE0) { // Check s[0] == 1110xxxx
+ *value = ((s[0] & 0x0F) << 12) | value16;
+ return 3;
+ }
+ if ((srclen < 4) || ((s[3] & 0xC0) != 0x80)) { // Check s[3] != 10xxxxxx
+ return 0;
+ }
+ value16 = (value16 << 6) | (s[3] & 0x3F);
+ if ((s[0] & 0xF8) == 0xF0) { // Check s[0] == 11110xxx
+ *value = ((s[0] & 0x07) << 18) | value16;
+ return 4;
+ }
+ return 0;
+}
+
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value) {
+ if ((value <= 0x7F) && (buflen >= 1)) {
+ buffer[0] = static_cast<unsigned char>(value);
+ return 1;
+ }
+ if ((value <= 0x7FF) && (buflen >= 2)) {
+ buffer[0] = 0xC0 | static_cast<unsigned char>(value >> 6);
+ buffer[1] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+ return 2;
+ }
+ if ((value <= 0xFFFF) && (buflen >= 3)) {
+ buffer[0] = 0xE0 | static_cast<unsigned char>(value >> 12);
+ buffer[1] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+ buffer[2] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+ return 3;
+ }
+ if ((value <= 0x1FFFFF) && (buflen >= 4)) {
+ buffer[0] = 0xF0 | static_cast<unsigned char>(value >> 18);
+ buffer[1] = 0x80 | static_cast<unsigned char>((value >> 12) & 0x3F);
+ buffer[2] = 0x80 | static_cast<unsigned char>((value >> 6) & 0x3F);
+ buffer[3] = 0x80 | static_cast<unsigned char>(value & 0x3F);
+ return 4;
+ }
+ return 0;
+}
+
+size_t html_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ unsigned char ch = source[srcpos];
+ if (ch < 128) {
+ srcpos += 1;
+ if (ASCII_CLASS[ch] & HTML_UNSAFE) {
+ const char * escseq = 0;
+ size_t esclen = 0;
+ switch (ch) {
+ case '<': escseq = "<"; esclen = 4; break;
+ case '>': escseq = ">"; esclen = 4; break;
+ case '\'': escseq = "'"; esclen = 5; break;
+ case '\"': escseq = """; esclen = 6; break;
+ case '&': escseq = "&"; esclen = 5; break;
+ default: ASSERT(false);
+ }
+ if (bufpos + esclen >= buflen) {
+ break;
+ }
+ memcpy(buffer + bufpos, escseq, esclen);
+ bufpos += esclen;
+ } else {
+ buffer[bufpos++] = ch;
+ }
+ } else {
+ // Largest value is 0x1FFFFF => � (10 characters)
+ char escseq[11];
+ unsigned long val;
+ if (size_t vallen = utf8_decode(&source[srcpos], srclen - srcpos, &val)) {
+ srcpos += vallen;
+ } else {
+ // Not a valid utf8 sequence, just use the raw character.
+ val = static_cast<unsigned char>(source[srcpos++]);
+ }
+ size_t esclen = sprintfn(escseq, ARRAY_SIZE(escseq), "&#%lu;", val);
+ if (bufpos + esclen >= buflen) {
+ break;
+ }
+ memcpy(buffer + bufpos, escseq, esclen);
+ bufpos += esclen;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t html_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ return xml_decode(buffer, buflen, source, srclen);
+}
+
+size_t xml_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ unsigned char ch = source[srcpos++];
+ if ((ch < 128) && (ASCII_CLASS[ch] & XML_UNSAFE)) {
+ const char * escseq = 0;
+ size_t esclen = 0;
+ switch (ch) {
+ case '<': escseq = "<"; esclen = 4; break;
+ case '>': escseq = ">"; esclen = 4; break;
+ case '\'': escseq = "'"; esclen = 6; break;
+ case '\"': escseq = """; esclen = 6; break;
+ case '&': escseq = "&"; esclen = 5; break;
+ default: ASSERT(false);
+ }
+ if (bufpos + esclen >= buflen) {
+ break;
+ }
+ memcpy(buffer + bufpos, escseq, esclen);
+ bufpos += esclen;
+ } else {
+ buffer[bufpos++] = ch;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t xml_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos < srclen) && (bufpos + 1 < buflen)) {
+ unsigned char ch = source[srcpos++];
+ if (ch != '&') {
+ buffer[bufpos++] = ch;
+ } else if ((srcpos + 2 < srclen)
+ && (memcmp(source + srcpos, "lt;", 3) == 0)) {
+ buffer[bufpos++] = '<';
+ srcpos += 3;
+ } else if ((srcpos + 2 < srclen)
+ && (memcmp(source + srcpos, "gt;", 3) == 0)) {
+ buffer[bufpos++] = '>';
+ srcpos += 3;
+ } else if ((srcpos + 4 < srclen)
+ && (memcmp(source + srcpos, "apos;", 5) == 0)) {
+ buffer[bufpos++] = '\'';
+ srcpos += 5;
+ } else if ((srcpos + 4 < srclen)
+ && (memcmp(source + srcpos, "quot;", 5) == 0)) {
+ buffer[bufpos++] = '\"';
+ srcpos += 5;
+ } else if ((srcpos + 3 < srclen)
+ && (memcmp(source + srcpos, "amp;", 4) == 0)) {
+ buffer[bufpos++] = '&';
+ srcpos += 4;
+ } else if ((srcpos < srclen) && (source[srcpos] == '#')) {
+ int int_base = 10;
+ if ((srcpos + 1 < srclen) && (source[srcpos+1] == 'x')) {
+ int_base = 16;
+ srcpos += 1;
+ }
+ char * ptr;
+ // TODO: Fix hack (ptr may go past end of data)
+ unsigned long val = strtoul(source + srcpos + 1, &ptr, int_base);
+ if ((static_cast<size_t>(ptr - source) < srclen) && (*ptr == ';')) {
+ srcpos = ptr - source + 1;
+ } else {
+ // Not a valid escape sequence.
+ break;
+ }
+ if (size_t esclen = utf8_encode(buffer + bufpos, buflen - bufpos, val)) {
+ bufpos += esclen;
+ } else {
+ // Not enough room to encode the character, or illegal character
+ break;
+ }
+ } else {
+ // Unrecognized escape sequence.
+ break;
+ }
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+std::string hex_encode(const char * source, size_t srclen) {
+ const size_t kBufferSize = srclen * 2 + 1;
+ char* buffer = STACK_ARRAY(char, kBufferSize);
+ size_t length = hex_encode(buffer, kBufferSize, source, srclen);
+ return std::string(buffer, length);
+}
+
+size_t hex_encode(char * buffer, size_t buflen,
+ const char * csource, size_t srclen) {
+ ASSERT(NULL != buffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ const unsigned char * bsource =
+ reinterpret_cast<const unsigned char *>(csource);
+
+ size_t srcpos = 0, bufpos = 0;
+ srclen = _min(srclen, (buflen - 1) / 2);
+ while (srcpos < srclen) {
+ unsigned char ch = bsource[srcpos++];
+ buffer[bufpos ] = hex_encode((ch >> 4) & 0xF);
+ buffer[bufpos+1] = hex_encode((ch ) & 0xF);
+ bufpos += 2;
+ }
+ buffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t hex_decode(char * cbuffer, size_t buflen,
+ const char * source, size_t srclen) {
+ ASSERT(NULL != cbuffer); // TODO: estimate output size
+ if (buflen <= 0)
+ return 0;
+
+ unsigned char * bbuffer = reinterpret_cast<unsigned char *>(cbuffer);
+
+ unsigned char h1, h2;
+ size_t srcpos = 0, bufpos = 0;
+ while ((srcpos + 1 < srclen)
+ && (bufpos + 1 < buflen)
+ && hex_decode(source[srcpos], &h1)
+ && hex_decode(source[srcpos+1], &h2))
+ {
+ bbuffer[bufpos++] = (h1 << 4) | h2;
+ srcpos += 2;
+ }
+ bbuffer[bufpos] = '\0';
+ return bufpos;
+}
+
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+ Transform t) {
+ char* buffer = STACK_ARRAY(char, maxlen + 1);
+ size_t length = t(buffer, maxlen + 1, source.data(), source.length());
+ value.assign(buffer, length);
+ return length;
+}
+
+std::string s_transform(const std::string& source, Transform t) {
+ // Ask transformation function to approximate the destination size (returns upper bound)
+ size_t maxlen = t(NULL, 0, source.data(), source.length());
+ char * buffer = STACK_ARRAY(char, maxlen);
+ size_t len = t(buffer, maxlen, source.data(), source.length());
+ std::string result(buffer, len);
+ return result;
+}
+
+size_t tokenize(const std::string& source, char delimiter,
+ std::vector<std::string>* fields) {
+ ASSERT(NULL != fields);
+ fields->clear();
+ size_t last = 0;
+ for (size_t i = 0; i < source.length(); ++i) {
+ if (source[i] == delimiter) {
+ if (i != last) {
+ fields->push_back(source.substr(last, i - last));
+ }
+ last = i + 1;
+ }
+ }
+ if (last != source.length()) {
+ fields->push_back(source.substr(last, source.length() - last));
+ }
+ return fields->size();
+}
+
+size_t split(const std::string& source, char delimiter,
+ std::vector<std::string>* fields) {
+ ASSERT(NULL != fields);
+ fields->clear();
+ size_t last = 0;
+ for (size_t i = 0; i < source.length(); ++i) {
+ if (source[i] == delimiter) {
+ fields->push_back(source.substr(last, i - last));
+ last = i + 1;
+ }
+ }
+ fields->push_back(source.substr(last, source.length() - last));
+ return fields->size();
+}
+
+char make_char_safe_for_filename(char c) {
+ if (c < 32)
+ return '_';
+
+ switch (c) {
+ case '<':
+ case '>':
+ case ':':
+ case '"':
+ case '/':
+ case '\\':
+ case '|':
+ case '*':
+ case '?':
+ return '_';
+
+ default:
+ return c;
+ }
+}
+
+/*
+void sprintf(std::string& value, size_t maxlen, const char * format, ...) {
+ char * buffer = STACK_ARRAY(char, maxlen + 1);
+ va_list args;
+ va_start(args, format);
+ value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args));
+ va_end(args);
+}
+*/
+
+/////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/stringencode.h b/talk/base/stringencode.h
new file mode 100644
index 0000000..fd29f87
--- /dev/null
+++ b/talk/base/stringencode.h
@@ -0,0 +1,184 @@
+/*
+ * 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_BASE_STRINGENCODE_H__
+#define TALK_BASE_STRINGENCODE_H__
+
+#include <string>
+#include <sstream>
+#include <vector>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// String Encoding Utilities
+//////////////////////////////////////////////////////////////////////
+
+// Convert an unsigned value from 0 to 15 to the hex character equivalent...
+char hex_encode(unsigned char val);
+// ...and vice-versa.
+bool hex_decode(char ch, unsigned char* val);
+
+// Convert an unsigned value to it's utf8 representation. Returns the length
+// of the encoded string, or 0 if the encoding is longer than buflen - 1.
+size_t utf8_encode(char* buffer, size_t buflen, unsigned long value);
+// Decode the utf8 encoded value pointed to by source. Returns the number of
+// bytes used by the encoding, or 0 if the encoding is invalid.
+size_t utf8_decode(const char* source, size_t srclen, unsigned long* value);
+
+// Escaping prefixes illegal characters with the escape character. Compact, but
+// illegal characters still appear in the string.
+size_t escape(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ const char * illegal, char escape);
+// Note: in-place unescaping (buffer == source) is allowed.
+size_t unescape(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ char escape);
+
+// Encoding replaces illegal characters with the escape character and 2 hex
+// chars, so it's a little less compact than escape, but completely removes
+// illegal characters. note that hex digits should not be used as illegal
+// characters.
+size_t encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ const char * illegal, char escape);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen,
+ char escape);
+
+// Returns a list of characters that may be unsafe for use in the name of a
+// file, suitable for passing to the 'illegal' member of escape or encode.
+const char* unsafe_filename_characters();
+
+// url_encode is an encode operation with a predefined set of illegal characters
+// and escape character (for use in URLs, obviously).
+size_t url_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t url_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+
+// html_encode prevents data embedded in html from containing markup.
+size_t html_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t html_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+
+// xml_encode makes data suitable for inside xml attributes and values.
+size_t xml_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+// Note: in-place decoding (buffer == source) is allowed.
+size_t xml_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+
+// hex_encode shows the hex representation of binary data in ascii.
+size_t hex_encode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+size_t hex_decode(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+// helper funtion for hex_encode
+std::string hex_encode(const char * source, size_t srclen);
+
+// Apply any suitable string transform (including the ones above) to an STL
+// string. Stack-allocated temporary space is used for the transformation,
+// so value and source may refer to the same string.
+typedef size_t (*Transform)(char * buffer, size_t buflen,
+ const char * source, size_t srclen);
+size_t transform(std::string& value, size_t maxlen, const std::string& source,
+ Transform t);
+
+// Return the result of applying transform t to source.
+std::string s_transform(const std::string& source, Transform t);
+
+// Convenience wrappers
+inline std::string s_url_encode(const std::string& source) {
+ return s_transform(source, url_encode);
+}
+inline std::string s_url_decode(const std::string& source) {
+ return s_transform(source, url_decode);
+}
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter creating empty fields.
+size_t split(const std::string& source, char delimiter,
+ std::vector<std::string>* fields);
+
+// Splits the source string into multiple fields separated by delimiter,
+// with duplicates of delimiter ignored. Trailing delimiter ignored.
+size_t tokenize(const std::string& source, char delimiter,
+ std::vector<std::string>* fields);
+
+// Safe sprintf to std::string
+//void sprintf(std::string& value, size_t maxlen, const char * format, ...)
+// PRINTF_FORMAT(3);
+
+// Convert arbitrary values to/from a string.
+
+template <class T>
+static bool ToString(const T &t, std::string* s) {
+ std::ostringstream oss;
+ oss << t;
+ *s = oss.str();
+ return !oss.fail();
+}
+
+template <class T>
+static bool FromString(const std::string& s, T* t) {
+ std::istringstream iss(s);
+ iss >> *t;
+ return !iss.fail();
+}
+
+// Inline versions of the string conversion routines.
+
+template<typename T>
+static inline std::string ToString(T val) {
+ std::string str; ToString(val, &str); return str;
+}
+
+template<typename T>
+static inline T FromString(const std::string& str) {
+ T val; FromString(str, &val); return val;
+}
+
+template<typename T>
+static inline T FromString(const T& defaultValue, const std::string& str) {
+ T val(defaultValue); FromString(str, &val); return val;
+}
+
+// simple function to strip out characters which shouldn't be
+// used in filenames
+char make_char_safe_for_filename(char c);
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_STRINGENCODE_H__
diff --git a/talk/base/stringutils.cc b/talk/base/stringutils.cc
new file mode 100644
index 0000000..8d8b7e0
--- /dev/null
+++ b/talk/base/stringutils.cc
@@ -0,0 +1,145 @@
+/*
+ * 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 "talk/base/stringutils.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+bool memory_check(const void* memory, int c, size_t count) {
+ const char* char_memory = static_cast<const char*>(memory);
+ char char_c = static_cast<char>(c);
+ for (size_t i = 0; i < count; ++i) {
+ if (char_memory[i] != char_c) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool string_match(const char* target, const char* pattern) {
+ while (*pattern) {
+ if (*pattern == '*') {
+ if (!*++pattern) {
+ return true;
+ }
+ while (*target) {
+ if ((toupper(*pattern) == toupper(*target))
+ && string_match(target + 1, pattern + 1)) {
+ return true;
+ }
+ ++target;
+ }
+ return false;
+ } else {
+ if (toupper(*pattern) != toupper(*target)) {
+ return false;
+ }
+ ++target;
+ ++pattern;
+ }
+ }
+ return !*target;
+}
+
+#ifdef WIN32
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+ CharacterTransformation transformation) {
+ wchar_t c1, c2;
+ while (true) {
+ if (n-- == 0) return 0;
+ c1 = transformation(*s1);
+ // Double check that characters are not UTF-8
+ ASSERT(static_cast<unsigned char>(*s2) < 128);
+ // Note: *s2 gets implicitly promoted to wchar_t
+ c2 = transformation(*s2);
+ if (c1 != c2) return (c1 < c2) ? -1 : 1;
+ if (!c1) return 0;
+ ++s1;
+ ++s2;
+ }
+}
+
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+ const char* source, size_t srclen) {
+ if (buflen <= 0)
+ return 0;
+
+ if (srclen == SIZE_UNKNOWN) {
+ srclen = strlenn(source, buflen - 1);
+ } else if (srclen >= buflen) {
+ srclen = buflen - 1;
+ }
+#if _DEBUG
+ // Double check that characters are not UTF-8
+ for (size_t pos = 0; pos < srclen; ++pos)
+ ASSERT(static_cast<unsigned char>(source[pos]) < 128);
+#endif // _DEBUG
+ std::copy(source, source + srclen, buffer);
+ buffer[srclen] = 0;
+ return srclen;
+}
+
+#endif // WIN32
+
+void replace_substrs(const char *search,
+ size_t search_len,
+ const char *replace,
+ size_t replace_len,
+ std::string *s) {
+ size_t pos = 0;
+ while ((pos = s->find(search, pos, search_len)) != std::string::npos) {
+ s->replace(pos, search_len, replace, replace_len);
+ pos += replace_len;
+ }
+}
+
+bool starts_with(const char *s1, const char *s2) {
+ while (*s2 != '\0') {
+ if (*s1 != *s2) {
+ return false;
+ }
+ s1++;
+ s2++;
+ }
+ return true;
+}
+
+static const std::string kWhitespace(" \n\r\t");
+
+std::string string_trim(const std::string& s) {
+ std::string::size_type first = s.find_first_not_of(kWhitespace);
+ std::string::size_type last = s.find_last_not_of(kWhitespace);
+
+ if (first == std::string::npos || last == std::string::npos) {
+ return std::string("");
+ }
+
+ return s.substr(first, last - first + 1);
+}
+
+} // namespace talk_base
diff --git a/talk/base/stringutils.h b/talk/base/stringutils.h
new file mode 100644
index 0000000..6aa9b18
--- /dev/null
+++ b/talk/base/stringutils.h
@@ -0,0 +1,340 @@
+/*
+ * 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_STRINGUTILS_H__
+#define TALK_BASE_STRINGUTILS_H__
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#ifdef WIN32
+#include <malloc.h>
+#include <wchar.h>
+#define alloca _alloca
+#endif // WIN32
+
+#ifdef POSIX
+#ifdef BSD
+#include <stdlib.h>
+#else // BSD
+#include <alloca.h>
+#endif // !BSD
+#endif // POSIX
+
+#include <cstring>
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+///////////////////////////////////////////////////////////////////////////////
+// Generic string/memory utilities
+///////////////////////////////////////////////////////////////////////////////
+
+#define STACK_ARRAY(TYPE, LEN) static_cast<TYPE*>(::alloca((LEN)*sizeof(TYPE)))
+
+namespace talk_base {
+
+// Complement to memset. Verifies memory consists of count bytes of value c.
+bool memory_check(const void* memory, int c, size_t count);
+
+// Determines whether the simple wildcard pattern matches target.
+// Alpha characters in pattern match case-insensitively.
+// Asterisks in pattern match 0 or more characters.
+// Ex: string_match("www.TEST.GOOGLE.COM", "www.*.com") -> true
+bool string_match(const char* target, const char* pattern);
+
+} // namespace talk_base
+
+///////////////////////////////////////////////////////////////////////////////
+// Rename a bunch of common string functions so they are consistent across
+// platforms and between char and wchar_t variants.
+// Here is the full list of functions that are unified:
+// strlen, strcmp, stricmp, strncmp, strnicmp
+// strchr, vsnprintf, strtoul, tolowercase
+// tolowercase is like tolower, but not compatible with end-of-file value
+//
+// 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.
+///////////////////////////////////////////////////////////////////////////////
+
+inline char tolowercase(char c) {
+ return static_cast<char>(tolower(c));
+}
+
+#ifdef WIN32
+
+inline size_t strlen(const wchar_t* s) {
+ return wcslen(s);
+}
+inline int strcmp(const wchar_t* s1, const wchar_t* s2) {
+ return wcscmp(s1, s2);
+}
+inline int stricmp(const wchar_t* s1, const wchar_t* s2) {
+ return _wcsicmp(s1, s2);
+}
+inline int strncmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+ return wcsncmp(s1, s2, n);
+}
+inline int strnicmp(const wchar_t* s1, const wchar_t* s2, size_t n) {
+ return _wcsnicmp(s1, s2, n);
+}
+inline const wchar_t* strchr(const wchar_t* s, wchar_t c) {
+ return wcschr(s, c);
+}
+inline const wchar_t* strstr(const wchar_t* haystack, const wchar_t* needle) {
+ return wcsstr(haystack, needle);
+}
+#ifndef vsnprintf
+inline int vsnprintf(char* buf, size_t n, const char* fmt, va_list args) {
+ return _vsnprintf(buf, n, fmt, args);
+}
+inline int vsnprintf(wchar_t* buf, size_t n, const wchar_t* fmt, va_list args) {
+ return _vsnwprintf(buf, n, fmt, args);
+}
+#endif // !vsnprintf
+inline unsigned long strtoul(const wchar_t* snum, wchar_t** end, int base) {
+ return wcstoul(snum, end, base);
+}
+inline wchar_t tolowercase(wchar_t c) {
+ return static_cast<wchar_t>(towlower(c));
+}
+
+#endif // WIN32
+
+#ifdef POSIX
+
+inline int _stricmp(const char* s1, const char* s2) {
+ return strcasecmp(s1, s2);
+}
+inline int _strnicmp(const char* s1, const char* s2, size_t n) {
+ return strncasecmp(s1, s2, n);
+}
+
+#endif // POSIX
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits simplifies porting string functions to be CTYPE-agnostic
+///////////////////////////////////////////////////////////////////////////////
+
+namespace talk_base {
+
+const size_t SIZE_UNKNOWN = static_cast<size_t>(-1);
+
+template<class CTYPE>
+struct Traits {
+ // STL string type
+ //typedef XXX string;
+ // Null-terminated string
+ //inline static const CTYPE* empty_str();
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// String utilities which work with char or wchar_t
+///////////////////////////////////////////////////////////////////////////////
+
+template<class CTYPE>
+inline const CTYPE* nonnull(const CTYPE* str, const CTYPE* def_str = NULL) {
+ return str ? str : (def_str ? def_str : Traits<CTYPE>::empty_str());
+}
+
+template<class CTYPE>
+const CTYPE* strchr(const CTYPE* str, const CTYPE* chs) {
+ for (size_t i=0; str[i]; ++i) {
+ for (size_t j=0; chs[j]; ++j) {
+ if (str[i] == chs[j]) {
+ return str + i;
+ }
+ }
+ }
+ return 0;
+}
+
+template<class CTYPE>
+const CTYPE* strchrn(const CTYPE* str, size_t slen, CTYPE ch) {
+ for (size_t i=0; i<slen && str[i]; ++i) {
+ if (str[i] == ch) {
+ return str + i;
+ }
+ }
+ return 0;
+}
+
+template<class CTYPE>
+size_t strlenn(const CTYPE* buffer, size_t buflen) {
+ size_t bufpos = 0;
+ while (buffer[bufpos] && (bufpos < buflen)) {
+ ++bufpos;
+ }
+ return bufpos;
+}
+
+// Safe versions of strncpy, strncat, snprintf and vsnprintf that always
+// null-terminate.
+
+template<class CTYPE>
+size_t strcpyn(CTYPE* buffer, size_t buflen,
+ const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+ if (buflen <= 0)
+ return 0;
+
+ if (srclen == SIZE_UNKNOWN) {
+ srclen = strlenn(source, buflen - 1);
+ } else if (srclen >= buflen) {
+ srclen = buflen - 1;
+ }
+ memcpy(buffer, source, srclen * sizeof(CTYPE));
+ buffer[srclen] = 0;
+ return srclen;
+}
+
+template<class CTYPE>
+size_t strcatn(CTYPE* buffer, size_t buflen,
+ const CTYPE* source, size_t srclen = SIZE_UNKNOWN) {
+ if (buflen <= 0)
+ return 0;
+
+ size_t bufpos = strlenn(buffer, buflen - 1);
+ return bufpos + strcpyn(buffer + bufpos, buflen - bufpos, source, srclen);
+}
+
+// Some compilers (clang specifically) require vsprintfn be defined before
+// sprintfn.
+template<class CTYPE>
+size_t vsprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format,
+ va_list args) {
+ int len = vsnprintf(buffer, buflen, format, args);
+ if ((len < 0) || (static_cast<size_t>(len) >= buflen)) {
+ len = static_cast<int>(buflen - 1);
+ buffer[len] = 0;
+ }
+ return len;
+}
+
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...);
+/* This works to get GCC to notice printf argument mismatches, but then complains of missing implementation of sprintfn<char>
+template<>
+size_t sprintfn(char* buffer, size_t buflen, const char* format, ...)
+GCC_ATTR(format(printf,3,4));
+*/
+template<class CTYPE>
+size_t sprintfn(CTYPE* buffer, size_t buflen, const CTYPE* format, ...) {
+ va_list args;
+ va_start(args, format);
+ size_t len = vsprintfn(buffer, buflen, format, args);
+ va_end(args);
+ return len;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Allow safe comparing and copying ascii (not UTF-8) with both wide and
+// non-wide character strings.
+///////////////////////////////////////////////////////////////////////////////
+
+inline int asccmp(const char* s1, const char* s2) {
+ return strcmp(s1, s2);
+}
+inline int ascicmp(const char* s1, const char* s2) {
+ return _stricmp(s1, s2);
+}
+inline int ascncmp(const char* s1, const char* s2, size_t n) {
+ return strncmp(s1, s2, n);
+}
+inline int ascnicmp(const char* s1, const char* s2, size_t n) {
+ return _strnicmp(s1, s2, n);
+}
+inline size_t asccpyn(char* buffer, size_t buflen,
+ const char* source, size_t srclen = SIZE_UNKNOWN) {
+ return strcpyn(buffer, buflen, source, srclen);
+}
+
+#ifdef WIN32
+
+typedef wchar_t(*CharacterTransformation)(wchar_t);
+inline wchar_t identity(wchar_t c) { return c; }
+int ascii_string_compare(const wchar_t* s1, const char* s2, size_t n,
+ CharacterTransformation transformation);
+
+inline int asccmp(const wchar_t* s1, const char* s2) {
+ return ascii_string_compare(s1, s2, static_cast<size_t>(-1), identity);
+}
+inline int ascicmp(const wchar_t* s1, const char* s2) {
+ return ascii_string_compare(s1, s2, static_cast<size_t>(-1), tolowercase);
+}
+inline int ascncmp(const wchar_t* s1, const char* s2, size_t n) {
+ return ascii_string_compare(s1, s2, n, identity);
+}
+inline int ascnicmp(const wchar_t* s1, const char* s2, size_t n) {
+ return ascii_string_compare(s1, s2, n, tolowercase);
+}
+size_t asccpyn(wchar_t* buffer, size_t buflen,
+ const char* source, size_t srclen = SIZE_UNKNOWN);
+
+#endif // WIN32
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<char> specializations
+///////////////////////////////////////////////////////////////////////////////
+
+template<>
+struct Traits<char> {
+ typedef std::string string;
+ inline static const char* empty_str() { return ""; }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Traits<wchar_t> specializations (Windows only, currently)
+///////////////////////////////////////////////////////////////////////////////
+
+#ifdef WIN32
+
+template<>
+struct Traits<wchar_t> {
+ typedef std::wstring string;
+ inline static const wchar_t* Traits<wchar_t>::empty_str() { return L""; }
+};
+
+#endif // WIN32
+
+// Replaces all occurrences of "search" with "replace".
+void replace_substrs(const char *search,
+ size_t search_len,
+ const char *replace,
+ size_t replace_len,
+ std::string *s);
+
+// True iff s1 starts with s2.
+bool starts_with(const char *s1, const char *s2);
+
+// Remove leading and trailing whitespaces.
+std::string string_trim(const std::string& s);
+
+} // namespace talk_base
+
+#endif // TALK_BASE_STRINGUTILS_H__
diff --git a/talk/base/task.cc b/talk/base/task.cc
new file mode 100644
index 0000000..ad17438
--- /dev/null
+++ b/talk/base/task.cc
@@ -0,0 +1,296 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/task.h"
+#include "talk/base/common.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+int32 Task::unique_id_seed_ = 0;
+
+Task::Task(TaskParent *parent)
+ : TaskParent(this, parent),
+ state_(STATE_INIT),
+ blocked_(false),
+ done_(false),
+ aborted_(false),
+ busy_(false),
+ error_(false),
+ start_time_(0),
+ timeout_time_(0),
+ timeout_seconds_(0),
+ timeout_suspended_(false) {
+ unique_id_ = unique_id_seed_++;
+
+ // sanity check that we didn't roll-over our id seed
+ ASSERT(unique_id_ < unique_id_seed_);
+}
+
+Task::~Task() {
+ // Is this task being deleted in the correct manner?
+ ASSERT(!done_ || GetRunner()->is_ok_to_delete(this));
+ ASSERT(state_ == STATE_INIT || done_);
+ ASSERT(state_ == STATE_INIT || blocked_);
+
+ // If the task is being deleted without being done, it
+ // means that it hasn't been removed from its parent.
+ // This happens if a task is deleted outside of TaskRunner.
+ if (!done_) {
+ Stop();
+ }
+}
+
+int64 Task::CurrentTime() {
+ return GetRunner()->CurrentTime();
+}
+
+int64 Task::ElapsedTime() {
+ return CurrentTime() - start_time_;
+}
+
+void Task::Start() {
+ if (state_ != STATE_INIT)
+ return;
+ // Set the start time before starting the task. Otherwise if the task
+ // finishes quickly and deletes the Task object, setting start_time_
+ // will crash.
+ start_time_ = CurrentTime();
+ GetRunner()->StartTask(this);
+}
+
+void Task::Step() {
+ if (done_) {
+#ifdef _DEBUG
+ // we do not know how !blocked_ happens when done_ - should be impossible.
+ // But it causes problems, so in retail build, we force blocked_, and
+ // under debug we assert.
+ ASSERT(blocked_);
+#else
+ blocked_ = true;
+#endif
+ return;
+ }
+
+ // Async Error() was called
+ if (error_) {
+ done_ = true;
+ state_ = STATE_ERROR;
+ blocked_ = true;
+// obsolete - an errored task is not considered done now
+// SignalDone();
+
+ Stop();
+#ifdef _DEBUG
+ // verify that stop removed this from its parent
+ ASSERT(!parent()->IsChildTask(this));
+#endif
+ return;
+ }
+
+ busy_ = true;
+ int new_state = Process(state_);
+ busy_ = false;
+
+ if (aborted_) {
+ Abort(true); // no need to wake because we're awake
+ return;
+ }
+
+ if (new_state == STATE_BLOCKED) {
+ blocked_ = true;
+ // Let the timeout continue
+ } else {
+ state_ = new_state;
+ blocked_ = false;
+ ResetTimeout();
+ }
+
+ if (new_state == STATE_DONE) {
+ done_ = true;
+ } else if (new_state == STATE_ERROR) {
+ done_ = true;
+ error_ = true;
+ }
+
+ if (done_) {
+// obsolete - call this yourself
+// SignalDone();
+
+ Stop();
+#if _DEBUG
+ // verify that stop removed this from its parent
+ ASSERT(!parent()->IsChildTask(this));
+#endif
+ blocked_ = true;
+ }
+}
+
+void Task::Abort(bool nowake) {
+ // Why only check for done_ (instead of "aborted_ || done_")?
+ //
+ // If aborted_ && !done_, it means the logic for aborting still
+ // needs to be executed (because busy_ must have been true when
+ // Abort() was previously called).
+ if (done_)
+ return;
+ aborted_ = true;
+ if (!busy_) {
+ done_ = true;
+ blocked_ = true;
+ error_ = true;
+
+ // "done_" is set before calling "Stop()" to ensure that this code
+ // doesn't execute more than once (recursively) for the same task.
+ Stop();
+#ifdef _DEBUG
+ // verify that stop removed this from its parent
+ ASSERT(!parent()->IsChildTask(this));
+#endif
+ if (!nowake) {
+ // WakeTasks to self-delete.
+ // Don't call Wake() because it is a no-op after "done_" is set.
+ // Even if Wake() did run, it clears "blocked_" which isn't desireable.
+ GetRunner()->WakeTasks();
+ }
+ }
+}
+
+void Task::Wake() {
+ if (done_)
+ return;
+ if (blocked_) {
+ blocked_ = false;
+ GetRunner()->WakeTasks();
+ }
+}
+
+void Task::Error() {
+ if (error_ || done_)
+ return;
+ error_ = true;
+ Wake();
+}
+
+std::string Task::GetStateName(int state) const {
+ static const std::string STR_BLOCKED("BLOCKED");
+ static const std::string STR_INIT("INIT");
+ static const std::string STR_START("START");
+ static const std::string STR_DONE("DONE");
+ static const std::string STR_ERROR("ERROR");
+ static const std::string STR_RESPONSE("RESPONSE");
+ static const std::string STR_HUH("??");
+ switch (state) {
+ case STATE_BLOCKED: return STR_BLOCKED;
+ case STATE_INIT: return STR_INIT;
+ case STATE_START: return STR_START;
+ case STATE_DONE: return STR_DONE;
+ case STATE_ERROR: return STR_ERROR;
+ case STATE_RESPONSE: return STR_RESPONSE;
+ }
+ return STR_HUH;
+}
+
+int Task::Process(int state) {
+ int newstate = STATE_ERROR;
+
+ if (TimedOut()) {
+ ClearTimeout();
+ newstate = OnTimeout();
+ SignalTimeout();
+ } else {
+ switch (state) {
+ case STATE_INIT:
+ newstate = STATE_START;
+ break;
+ case STATE_START:
+ newstate = ProcessStart();
+ break;
+ case STATE_RESPONSE:
+ newstate = ProcessResponse();
+ break;
+ case STATE_DONE:
+ case STATE_ERROR:
+ newstate = STATE_BLOCKED;
+ break;
+ }
+ }
+
+ return newstate;
+}
+
+void Task::Stop() {
+ // No need to wake because we're either awake or in abort
+ TaskParent::OnStopped(this);
+}
+
+void Task::set_timeout_seconds(const int timeout_seconds) {
+ timeout_seconds_ = timeout_seconds;
+ ResetTimeout();
+}
+
+bool Task::TimedOut() {
+ return timeout_seconds_ &&
+ timeout_time_ &&
+ CurrentTime() >= timeout_time_;
+}
+
+void Task::ResetTimeout() {
+ int64 previous_timeout_time = timeout_time_;
+ bool timeout_allowed = (state_ != STATE_INIT)
+ && (state_ != STATE_DONE)
+ && (state_ != STATE_ERROR);
+ if (timeout_seconds_ && timeout_allowed && !timeout_suspended_)
+ timeout_time_ = CurrentTime() +
+ (timeout_seconds_ * kSecToMsec * kMsecTo100ns);
+ else
+ timeout_time_ = 0;
+
+ GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::ClearTimeout() {
+ int64 previous_timeout_time = timeout_time_;
+ timeout_time_ = 0;
+ GetRunner()->UpdateTaskTimeout(this, previous_timeout_time);
+}
+
+void Task::SuspendTimeout() {
+ if (!timeout_suspended_) {
+ timeout_suspended_ = true;
+ ResetTimeout();
+ }
+}
+
+void Task::ResumeTimeout() {
+ if (timeout_suspended_) {
+ timeout_suspended_ = false;
+ ResetTimeout();
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/task.h b/talk/base/task.h
new file mode 100644
index 0000000..10e6f5c
--- /dev/null
+++ b/talk/base/task.h
@@ -0,0 +1,194 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_TASK_H__
+#define TALK_BASE_TASK_H__
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+/////////////////////////////////////////////////////////////////////
+//
+// TASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// Task is a state machine infrastructure. States are pushed forward by
+// pushing forwards a TaskRunner that holds on to all Tasks. The purpose
+// of Task is threefold:
+//
+// (1) It manages ongoing work on the UI thread. Multitasking without
+// threads, keeping it easy, keeping it real. :-) It does this by
+// organizing a set of states for each task. When you return from your
+// Process*() function, you return an integer for the next state. You do
+// not go onto the next state yourself. Every time you enter a state,
+// you check to see if you can do anything yet. If not, you return
+// STATE_BLOCKED. If you _could_ do anything, do not return
+// STATE_BLOCKED - even if you end up in the same state, return
+// STATE_mysamestate. When you are done, return STATE_DONE and then the
+// task will self-delete sometime afterwards.
+//
+// (2) It helps you avoid all those reentrancy problems when you chain
+// too many triggers on one thread. Basically if you want to tell a task
+// to process something for you, you feed your task some information and
+// then you Wake() it. Don't tell it to process it right away. If it
+// might be working on something as you send it information, you may want
+// to have a queue in the task.
+//
+// (3) Finally it helps manage parent tasks and children. If a parent
+// task gets aborted, all the children tasks are too. The nice thing
+// about this, for example, is if you have one parent task that
+// represents, say, and Xmpp connection, then you can spawn a whole bunch
+// of infinite lifetime child tasks and now worry about cleaning them up.
+// When the parent task goes to STATE_DONE, the task engine will make
+// sure all those children are aborted and get deleted.
+//
+// Notice that Task has a few built-in states, e.g.,
+//
+// STATE_INIT - the task isn't running yet
+// STATE_START - the task is in its first state
+// STATE_RESPONSE - the task is in its second state
+// STATE_DONE - the task is done
+//
+// STATE_ERROR - indicates an error - we should audit the error code in
+// light of any usage of it to see if it should be improved. When I
+// first put down the task stuff I didn't have a good sense of what was
+// needed for Abort and Error, and now the subclasses of Task will ground
+// the design in a stronger way.
+//
+// STATE_NEXT - the first undefined state number. (like WM_USER) - you
+// can start defining more task states there.
+//
+// When you define more task states, just override Process(int state) and
+// add your own switch statement. If you want to delegate to
+// Task::Process, you can effectively delegate to its switch statement.
+// No fancy method pointers or such - this is all just pretty low tech,
+// easy to debug, and fast.
+//
+// Also notice that Task has some primitive built-in timeout functionality.
+//
+// A timeout is defined as "the task stays in STATE_BLOCKED longer than
+// timeout_seconds_."
+//
+// Descendant classes can override this behavior by calling the
+// various protected methods to change the timeout behavior. For
+// instance, a descendand might call SuspendTimeout() when it knows
+// that it isn't waiting for anything that might timeout, but isn't
+// yet in the STATE_DONE state.
+//
+
+namespace talk_base {
+
+// Executes a sequence of steps
+class Task : public TaskParent {
+ public:
+ Task(TaskParent *parent);
+ virtual ~Task();
+
+ int32 unique_id() { return unique_id_; }
+
+ void Start();
+ void Step();
+ int GetState() const { return state_; }
+ bool HasError() const { return (GetState() == STATE_ERROR); }
+ bool Blocked() const { return blocked_; }
+ bool IsDone() const { return done_; }
+ int64 ElapsedTime();
+
+ // Called from outside to stop task without any more callbacks
+ void Abort(bool nowake = false);
+
+ bool TimedOut();
+
+ int64 timeout_time() const { return timeout_time_; }
+ int timeout_seconds() const { return timeout_seconds_; }
+ void set_timeout_seconds(int timeout_seconds);
+
+ sigslot::signal0<> SignalTimeout;
+
+ // Called inside the task to signal that the task may be unblocked
+ void Wake();
+
+ protected:
+
+ enum {
+ STATE_BLOCKED = -1,
+ STATE_INIT = 0,
+ STATE_START = 1,
+ STATE_DONE = 2,
+ STATE_ERROR = 3,
+ STATE_RESPONSE = 4,
+ STATE_NEXT = 5, // Subclasses which need more states start here and higher
+ };
+
+ // Called inside to advise that the task should wake and signal an error
+ void Error();
+
+ int64 CurrentTime();
+
+ virtual std::string GetStateName(int state) const;
+ virtual int Process(int state);
+ virtual void Stop();
+ virtual int ProcessStart() = 0;
+ virtual int ProcessResponse() { return STATE_DONE; }
+
+ void ResetTimeout();
+ void ClearTimeout();
+
+ void SuspendTimeout();
+ void ResumeTimeout();
+
+ protected:
+ virtual int OnTimeout() {
+ // by default, we are finished after timing out
+ return STATE_DONE;
+ }
+
+ private:
+ void Done();
+
+ int state_;
+ bool blocked_;
+ bool done_;
+ bool aborted_;
+ bool busy_;
+ bool error_;
+ int64 start_time_;
+ int64 timeout_time_;
+ int timeout_seconds_;
+ bool timeout_suspended_;
+ int32 unique_id_;
+
+ static int32 unique_id_seed_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TASK_H__
diff --git a/talk/base/taskparent.cc b/talk/base/taskparent.cc
new file mode 100644
index 0000000..f05ee82
--- /dev/null
+++ b/talk/base/taskparent.cc
@@ -0,0 +1,112 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/taskparent.h"
+
+#include "talk/base/task.h"
+#include "talk/base/taskrunner.h"
+
+namespace talk_base {
+
+TaskParent::TaskParent(Task* derived_instance, TaskParent *parent)
+ : parent_(parent) {
+ ASSERT(derived_instance != NULL);
+ ASSERT(parent != NULL);
+ runner_ = parent->GetRunner();
+ parent_->AddChild(derived_instance);
+ Initialize();
+}
+
+TaskParent::TaskParent(TaskRunner *derived_instance)
+ : parent_(NULL),
+ runner_(derived_instance) {
+ ASSERT(derived_instance != NULL);
+ Initialize();
+}
+
+// Does common initialization of member variables
+void TaskParent::Initialize() {
+ children_.reset(new ChildSet());
+ child_error_ = false;
+}
+
+void TaskParent::AddChild(Task *child) {
+ children_->insert(child);
+}
+
+#ifdef _DEBUG
+bool TaskParent::IsChildTask(Task *task) {
+ ASSERT(task != NULL);
+ return task->parent_ == this && children_->find(task) != children_->end();
+}
+#endif
+
+bool TaskParent::AllChildrenDone() {
+ for (ChildSet::iterator it = children_->begin();
+ it != children_->end();
+ ++it) {
+ if (!(*it)->IsDone())
+ return false;
+ }
+ return true;
+}
+
+bool TaskParent::AnyChildError() {
+ return child_error_;
+}
+
+void TaskParent::AbortAllChildren() {
+ if (children_->size() > 0) {
+#ifdef _DEBUG
+ runner_->IncrementAbortCount();
+#endif
+
+ ChildSet copy = *children_;
+ for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) {
+ (*it)->Abort(true); // Note we do not wake
+ }
+
+#ifdef _DEBUG
+ runner_->DecrementAbortCount();
+#endif
+ }
+}
+
+void TaskParent::OnStopped(Task *task) {
+ AbortAllChildren();
+ parent_->OnChildStopped(task);
+}
+
+void TaskParent::OnChildStopped(Task *child) {
+ if (child->HasError())
+ child_error_ = true;
+ children_->erase(child);
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskparent.h b/talk/base/taskparent.h
new file mode 100644
index 0000000..a6c5795
--- /dev/null
+++ b/talk/base/taskparent.h
@@ -0,0 +1,89 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_TASKPARENT_H__
+#define TALK_BASE_TASKPARENT_H__
+
+#include <set>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/scoped_ptr.h"
+
+namespace talk_base {
+
+class Task;
+class TaskRunner;
+
+class TaskParent {
+ public:
+ TaskParent(Task *derived_instance, TaskParent *parent);
+ explicit TaskParent(TaskRunner *derived_instance);
+ virtual ~TaskParent() { }
+
+ TaskParent *GetParent() { return parent_; }
+ TaskRunner *GetRunner() { return runner_; }
+
+ // Retrieves a parent that corresponds to the given "code". The code
+ // should be defined in a unique manner for the given subtree. This
+ // method will crash (when the parent_ is NULL) if there is no corresponding
+ // parent.
+ //
+ // Example use:
+ // XmppClient* client =
+ // static_cast<XmppClient*>(parent->GetParent(XMPP_CLIENT_TASK_CODE));
+ virtual TaskParent *GetParent(int code) { return parent_->GetParent(code); }
+
+ bool AllChildrenDone();
+ bool AnyChildError();
+#ifdef _DEBUG
+ bool IsChildTask(Task *task);
+#endif
+
+ protected:
+ void OnStopped(Task *task);
+ void AbortAllChildren();
+ TaskParent *parent() {
+ return parent_;
+ }
+
+ private:
+ void Initialize();
+ void OnChildStopped(Task *child);
+ void AddChild(Task *child);
+
+ TaskParent *parent_;
+ TaskRunner *runner_;
+ bool child_error_;
+ typedef std::set<Task *> ChildSet;
+ scoped_ptr<ChildSet> children_;
+ DISALLOW_EVIL_CONSTRUCTORS(TaskParent);
+};
+
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TASKPARENT_H__
diff --git a/talk/base/taskrunner.cc b/talk/base/taskrunner.cc
new file mode 100644
index 0000000..0c0816c
--- /dev/null
+++ b/talk/base/taskrunner.cc
@@ -0,0 +1,241 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/taskrunner.h"
+
+#include "talk/base/common.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/task.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+TaskRunner::TaskRunner()
+ : TaskParent(this),
+ next_timeout_task_(NULL),
+ tasks_running_(false)
+#ifdef _DEBUG
+ , abort_count_(0),
+ deleting_task_(NULL)
+#endif
+{
+}
+
+TaskRunner::~TaskRunner() {
+ // this kills and deletes children silently!
+ AbortAllChildren();
+ InternalRunTasks(true);
+}
+
+void TaskRunner::StartTask(Task * task) {
+ tasks_.push_back(task);
+
+ // the task we just started could be about to timeout --
+ // make sure our "next timeout task" is correct
+ UpdateTaskTimeout(task, 0);
+
+ WakeTasks();
+}
+
+void TaskRunner::RunTasks() {
+ InternalRunTasks(false);
+}
+
+void TaskRunner::InternalRunTasks(bool in_destructor) {
+ // This shouldn't run while an abort is happening.
+ // If that occurs, then tasks may be deleted in this method,
+ // but pointers to them will still be in the
+ // "ChildSet copy" in TaskParent::AbortAllChildren.
+ // Subsequent use of those task may cause data corruption or crashes.
+ ASSERT(!abort_count_);
+ // Running continues until all tasks are Blocked (ok for a small # of tasks)
+ if (tasks_running_) {
+ return; // don't reenter
+ }
+
+ tasks_running_ = true;
+
+ int64 previous_timeout_time = next_task_timeout();
+
+ int did_run = true;
+ while (did_run) {
+ did_run = false;
+ // use indexing instead of iterators because tasks_ may grow
+ for (size_t i = 0; i < tasks_.size(); ++i) {
+ while (!tasks_[i]->Blocked()) {
+ tasks_[i]->Step();
+ did_run = true;
+ }
+ }
+ }
+ // Tasks are deleted when running has paused
+ bool need_timeout_recalc = false;
+ for (size_t i = 0; i < tasks_.size(); ++i) {
+ if (tasks_[i]->IsDone()) {
+ Task* task = tasks_[i];
+ if (next_timeout_task_ &&
+ task->unique_id() == next_timeout_task_->unique_id()) {
+ next_timeout_task_ = NULL;
+ need_timeout_recalc = true;
+ }
+
+#ifdef _DEBUG
+ deleting_task_ = task;
+#endif
+ delete task;
+#ifdef _DEBUG
+ deleting_task_ = NULL;
+#endif
+ tasks_[i] = NULL;
+ }
+ }
+ // Finally, remove nulls
+ std::vector<Task *>::iterator it;
+ it = std::remove(tasks_.begin(),
+ tasks_.end(),
+ reinterpret_cast<Task *>(NULL));
+
+ tasks_.erase(it, tasks_.end());
+
+ if (need_timeout_recalc)
+ RecalcNextTimeout(NULL);
+
+ // Make sure that adjustments are done to account
+ // for any timeout changes (but don't call this
+ // while being destroyed since it calls a pure virtual function).
+ if (!in_destructor)
+ CheckForTimeoutChange(previous_timeout_time);
+
+ tasks_running_ = false;
+}
+
+void TaskRunner::PollTasks() {
+ // see if our "next potentially timed-out task" has indeed timed out.
+ // If it has, wake it up, then queue up the next task in line
+ // Repeat while we have new timed-out tasks.
+ // TODO: We need to guard against WakeTasks not updating
+ // next_timeout_task_. Maybe also add documentation in the header file once
+ // we understand this code better.
+ Task* old_timeout_task = NULL;
+ while (next_timeout_task_ &&
+ old_timeout_task != next_timeout_task_ &&
+ next_timeout_task_->TimedOut()) {
+ old_timeout_task = next_timeout_task_;
+ next_timeout_task_->Wake();
+ WakeTasks();
+ }
+}
+
+int64 TaskRunner::next_task_timeout() const {
+ if (next_timeout_task_) {
+ return next_timeout_task_->timeout_time();
+ }
+ return 0;
+}
+
+// this function gets called frequently -- when each task changes
+// state to something other than DONE, ERROR or BLOCKED, it calls
+// ResetTimeout(), which will call this function to make sure that
+// the next timeout-able task hasn't changed. The logic in this function
+// prevents RecalcNextTimeout() from getting called in most cases,
+// effectively making the task scheduler O-1 instead of O-N
+
+void TaskRunner::UpdateTaskTimeout(Task* task,
+ int64 previous_task_timeout_time) {
+ ASSERT(task != NULL);
+ int64 previous_timeout_time = next_task_timeout();
+ bool task_is_timeout_task = next_timeout_task_ != NULL &&
+ task->unique_id() == next_timeout_task_->unique_id();
+ if (task_is_timeout_task) {
+ previous_timeout_time = previous_task_timeout_time;
+ }
+
+ // if the relevant task has a timeout, then
+ // check to see if it's closer than the current
+ // "about to timeout" task
+ if (task->timeout_time()) {
+ if (next_timeout_task_ == NULL ||
+ (task->timeout_time() <= next_timeout_task_->timeout_time())) {
+ next_timeout_task_ = task;
+ }
+ } else if (task_is_timeout_task) {
+ // otherwise, if the task doesn't have a timeout,
+ // and it used to be our "about to timeout" task,
+ // walk through all the tasks looking for the real
+ // "about to timeout" task
+ RecalcNextTimeout(task);
+ }
+
+ // Note when task_running_, then the running routine
+ // (TaskRunner::InternalRunTasks) is responsible for calling
+ // CheckForTimeoutChange.
+ if (!tasks_running_) {
+ CheckForTimeoutChange(previous_timeout_time);
+ }
+}
+
+void TaskRunner::RecalcNextTimeout(Task *exclude_task) {
+ // walk through all the tasks looking for the one
+ // which satisfies the following:
+ // it's not finished already
+ // we're not excluding it
+ // it has the closest timeout time
+
+ int64 next_timeout_time = 0;
+ next_timeout_task_ = NULL;
+
+ for (size_t i = 0; i < tasks_.size(); ++i) {
+ Task *task = tasks_[i];
+ // if the task isn't complete, and it actually has a timeout time
+ if (!task->IsDone() && (task->timeout_time() > 0))
+ // if it doesn't match our "exclude" task
+ if (exclude_task == NULL ||
+ exclude_task->unique_id() != task->unique_id())
+ // if its timeout time is sooner than our current timeout time
+ if (next_timeout_time == 0 ||
+ task->timeout_time() <= next_timeout_time) {
+ // set this task as our next-to-timeout
+ next_timeout_time = task->timeout_time();
+ next_timeout_task_ = task;
+ }
+ }
+}
+
+void TaskRunner::CheckForTimeoutChange(int64 previous_timeout_time) {
+ int64 next_timeout = next_task_timeout();
+ bool timeout_change = (previous_timeout_time == 0 && next_timeout != 0) ||
+ next_timeout < previous_timeout_time ||
+ (previous_timeout_time <= CurrentTime() &&
+ previous_timeout_time != next_timeout);
+ if (timeout_change) {
+ OnTimeoutChange();
+ }
+}
+
+} // namespace talk_base
diff --git a/talk/base/taskrunner.h b/talk/base/taskrunner.h
new file mode 100644
index 0000000..f34a609
--- /dev/null
+++ b/talk/base/taskrunner.h
@@ -0,0 +1,117 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_TASKRUNNER_H__
+#define TALK_BASE_TASKRUNNER_H__
+
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/taskparent.h"
+
+namespace talk_base {
+class Task;
+
+const int64 kSecToMsec = 1000;
+const int64 kMsecTo100ns = 10000;
+const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns;
+
+class TaskRunner : public TaskParent, public sigslot::has_slots<> {
+ public:
+ TaskRunner();
+ virtual ~TaskRunner();
+
+ virtual void WakeTasks() = 0;
+
+ // Returns the current time in 100ns units. It is used for
+ // determining timeouts. The origin is not important, only
+ // the units and that rollover while the computer is running.
+ //
+ // On Windows, GetSystemTimeAsFileTime is the typical implementation.
+ virtual int64 CurrentTime() = 0 ;
+
+ void StartTask(Task *task);
+ void RunTasks();
+ void PollTasks();
+
+ void UpdateTaskTimeout(Task *task, int64 previous_task_timeout_time);
+
+#ifdef _DEBUG
+ bool is_ok_to_delete(Task* task) {
+ return task == deleting_task_;
+ }
+
+ void IncrementAbortCount() {
+ ++abort_count_;
+ }
+
+ void DecrementAbortCount() {
+ --abort_count_;
+ }
+#endif
+
+ // Returns the next absolute time when a task times out
+ // OR "0" if there is no next timeout.
+ int64 next_task_timeout() const;
+
+ protected:
+ // The primary usage of this method is to know if
+ // a callback timer needs to be set-up or adjusted.
+ // This method will be called
+ // * when the next_task_timeout() becomes a smaller value OR
+ // * when next_task_timeout() has changed values and the previous
+ // value is in the past.
+ //
+ // If the next_task_timeout moves to the future, this method will *not*
+ // get called (because it subclass should check next_task_timeout()
+ // when its timer goes off up to see if it needs to set-up a new timer).
+ //
+ // Note that this maybe called conservatively. In that it may be
+ // called when no time change has happened.
+ virtual void OnTimeoutChange() {
+ // by default, do nothing.
+ }
+
+ private:
+ void InternalRunTasks(bool in_destructor);
+ void CheckForTimeoutChange(int64 previous_timeout_time);
+
+ std::vector<Task *> tasks_;
+ Task *next_timeout_task_;
+ bool tasks_running_;
+#ifdef _DEBUG
+ int abort_count_;
+ Task* deleting_task_;
+#endif
+
+ void RecalcNextTimeout(Task *exclude_task);
+};
+
+} // namespace talk_base
+
+#endif // TASK_BASE_TASKRUNNER_H__
diff --git a/talk/base/thread.cc b/talk/base/thread.cc
new file mode 100644
index 0000000..28f3e48
--- /dev/null
+++ b/talk/base/thread.cc
@@ -0,0 +1,527 @@
+/*
+ * 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 "talk/base/thread.h"
+
+#if defined(WIN32)
+#include <comdef.h>
+#elif defined(POSIX)
+#include <time.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+#ifdef OSX_USE_COCOA
+#ifndef OSX
+#error OSX_USE_COCOA is defined but not OSX
+#endif
+#include "talk/base/maccocoathreadhelper.h"
+#include "talk/base/scoped_autorelease_pool.h"
+#endif
+
+namespace talk_base {
+
+ThreadManager g_thmgr;
+
+#ifdef POSIX
+pthread_key_t ThreadManager::key_;
+
+ThreadManager::ThreadManager() {
+ pthread_key_create(&key_, NULL);
+ main_thread_ = WrapCurrentThread();
+#if defined(OSX_USE_COCOA)
+ InitCocoaMultiThreading();
+#endif
+}
+
+ThreadManager::~ThreadManager() {
+#ifdef OSX_USE_COCOA
+ // This is called during exit, at which point apparently no NSAutoreleasePools
+ // are available; but we might still need them to do cleanup (or we get the
+ // "no autoreleasepool in place, just leaking" warning when exiting).
+ ScopedAutoreleasePool pool;
+#endif
+ UnwrapCurrentThread();
+ // Unwrap deletes main_thread_ automatically.
+ pthread_key_delete(key_);
+}
+
+Thread *ThreadManager::CurrentThread() {
+ return static_cast<Thread *>(pthread_getspecific(key_));
+}
+
+void ThreadManager::SetCurrent(Thread *thread) {
+ pthread_setspecific(key_, thread);
+}
+#endif
+
+#ifdef WIN32
+DWORD ThreadManager::key_;
+
+ThreadManager::ThreadManager() {
+ key_ = TlsAlloc();
+ main_thread_ = WrapCurrentThread();
+}
+
+ThreadManager::~ThreadManager() {
+ UnwrapCurrentThread();
+ TlsFree(key_);
+}
+
+Thread *ThreadManager::CurrentThread() {
+ return static_cast<Thread *>(TlsGetValue(key_));
+}
+
+void ThreadManager::SetCurrent(Thread *thread) {
+ TlsSetValue(key_, thread);
+}
+#endif
+
+// static
+Thread *ThreadManager::WrapCurrentThread() {
+ Thread* result = CurrentThread();
+ if (NULL == result) {
+ result = new Thread();
+#if defined(WIN32)
+ // We explicitly ask for no rights other than synchronization.
+ // This gives us the best chance of succeeding.
+ result->thread_ = OpenThread(SYNCHRONIZE, FALSE, GetCurrentThreadId());
+ if (!result->thread_)
+ LOG_GLE(LS_ERROR) << "Unable to get handle to thread.";
+#elif defined(POSIX)
+ result->thread_ = pthread_self();
+#endif
+ result->owned_ = false;
+ result->started_ = true;
+ SetCurrent(result);
+ }
+
+ return result;
+}
+
+// static
+void ThreadManager::UnwrapCurrentThread() {
+ Thread* t = CurrentThread();
+ if (t && !(t->IsOwned())) {
+ // Clears the platform-specific thread-specific storage.
+ SetCurrent(NULL);
+#ifdef WIN32
+ if (!CloseHandle(t->thread_)) {
+ LOG_GLE(LS_ERROR) << "When unwrapping thread, failed to close handle.";
+ }
+#endif
+ t->started_ = false;
+ delete t;
+ }
+}
+
+void ThreadManager::Add(Thread *thread) {
+ CritScope cs(&crit_);
+ threads_.push_back(thread);
+}
+
+void ThreadManager::Remove(Thread *thread) {
+ CritScope cs(&crit_);
+ threads_.erase(std::remove(threads_.begin(), threads_.end(), thread),
+ threads_.end());
+}
+
+void ThreadManager::StopAllThreads_() {
+ // TODO: In order to properly implement, Threads need to be ref-counted.
+ CritScope cs(&g_thmgr.crit_);
+ for (size_t i = 0; i < g_thmgr.threads_.size(); ++i) {
+ g_thmgr.threads_[i]->Stop();
+ }
+}
+
+struct ThreadInit {
+ Thread* thread;
+ Runnable* runnable;
+};
+
+Thread::Thread(SocketServer* ss)
+ : MessageQueue(ss),
+ priority_(PRIORITY_NORMAL),
+ started_(false),
+ has_sends_(false),
+#if defined(WIN32)
+ thread_(NULL),
+#endif
+ owned_(true) {
+ g_thmgr.Add(this);
+ SetName("Thread", this); // default name
+}
+
+Thread::~Thread() {
+ Stop();
+ if (active_)
+ Clear(NULL);
+ g_thmgr.Remove(this);
+}
+
+bool Thread::SleepMs(int milliseconds) {
+#ifdef WIN32
+ ::Sleep(milliseconds);
+ return true;
+#else
+ // POSIX has both a usleep() and a nanosleep(), but the former is deprecated,
+ // so we use nanosleep() even though it has greater precision than necessary.
+ struct timespec ts;
+ ts.tv_sec = milliseconds / 1000;
+ ts.tv_nsec = (milliseconds % 1000) * 1000000;
+ int ret = nanosleep(&ts, NULL);
+ if (ret != 0) {
+ LOG_ERR(LS_WARNING) << "nanosleep() returning early";
+ return false;
+ }
+ return true;
+#endif
+}
+
+bool Thread::SetName(const std::string& name, const void* obj) {
+ if (started_) return false;
+ name_ = name;
+ if (obj) {
+ char buf[16];
+ sprintfn(buf, sizeof(buf), " 0x%p", obj);
+ name_ += buf;
+ }
+ return true;
+}
+
+bool Thread::SetPriority(ThreadPriority priority) {
+ if (started_) return false;
+ priority_ = priority;
+ return true;
+}
+
+bool Thread::Start(Runnable* runnable) {
+ ASSERT(owned_);
+ if (!owned_) return false;
+ ASSERT(!started_);
+ if (started_) return false;
+
+ ThreadInit* init = new ThreadInit;
+ init->thread = this;
+ init->runnable = runnable;
+#if defined(WIN32)
+ DWORD flags = 0;
+ if (priority_ != PRIORITY_NORMAL) {
+ flags = CREATE_SUSPENDED;
+ }
+ thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, init, flags,
+ NULL);
+ if (thread_) {
+ if (priority_ != PRIORITY_NORMAL) {
+ if (priority_ == PRIORITY_HIGH) {
+ ::SetThreadPriority(thread_, THREAD_PRIORITY_HIGHEST);
+ } else if (priority_ == PRIORITY_ABOVE_NORMAL) {
+ ::SetThreadPriority(thread_, THREAD_PRIORITY_ABOVE_NORMAL);
+ } else if (priority_ == PRIORITY_IDLE) {
+ ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE);
+ }
+ ::ResumeThread(thread_);
+ }
+ } else {
+ return false;
+ }
+#elif defined(POSIX)
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ if (priority_ != PRIORITY_NORMAL) {
+ if (priority_ == PRIORITY_IDLE) {
+ // There is no POSIX-standard way to set a below-normal priority for an
+ // individual thread (only whole process), so let's not support it.
+ LOG(LS_WARNING) << "PRIORITY_IDLE not supported";
+ } else {
+ // Set real-time round-robin policy.
+ if (pthread_attr_setschedpolicy(&attr, SCHED_RR) != 0) {
+ LOG(LS_ERROR) << "pthread_attr_setschedpolicy";
+ }
+ struct sched_param param;
+ if (pthread_attr_getschedparam(&attr, ¶m) != 0) {
+ LOG(LS_ERROR) << "pthread_attr_getschedparam";
+ } else {
+ // The numbers here are arbitrary.
+ if (priority_ == PRIORITY_HIGH) {
+ param.sched_priority = 6; // 6 = HIGH
+ } else {
+ ASSERT(priority_ == PRIORITY_ABOVE_NORMAL);
+ param.sched_priority = 4; // 4 = ABOVE_NORMAL
+ }
+ if (pthread_attr_setschedparam(&attr, ¶m) != 0) {
+ LOG(LS_ERROR) << "pthread_attr_setschedparam";
+ }
+ }
+ }
+ }
+ int error_code = pthread_create(&thread_, &attr, PreRun, init);
+ if (0 != error_code) {
+ LOG(LS_ERROR) << "Unable to create pthread, error " << error_code;
+ return false;
+ }
+#endif
+ started_ = true;
+ return true;
+}
+
+void Thread::Join() {
+ if (started_) {
+ ASSERT(!IsCurrent());
+#if defined(WIN32)
+ WaitForSingleObject(thread_, INFINITE);
+ CloseHandle(thread_);
+ thread_ = NULL;
+#elif defined(POSIX)
+ void *pv;
+ pthread_join(thread_, &pv);
+#endif
+ started_ = false;
+ }
+}
+
+#ifdef WIN32
+// As seen on MSDN.
+// http://msdn.microsoft.com/en-us/library/xcb2z8hs(VS.71).aspx
+#define MSDEV_SET_THREAD_NAME 0x406D1388
+typedef struct tagTHREADNAME_INFO {
+ DWORD dwType;
+ LPCSTR szName;
+ DWORD dwThreadID;
+ DWORD dwFlags;
+} THREADNAME_INFO;
+
+void SetThreadName(DWORD dwThreadID, LPCSTR szThreadName) {
+ THREADNAME_INFO info;
+ info.dwType = 0x1000;
+ info.szName = szThreadName;
+ info.dwThreadID = dwThreadID;
+ info.dwFlags = 0;
+
+ __try {
+ RaiseException(MSDEV_SET_THREAD_NAME, 0, sizeof(info) / sizeof(DWORD),
+ reinterpret_cast<DWORD*>(&info));
+ }
+ __except(EXCEPTION_CONTINUE_EXECUTION) {
+ }
+}
+#endif // WIN32
+
+void* Thread::PreRun(void* pv) {
+ ThreadInit* init = static_cast<ThreadInit*>(pv);
+ ThreadManager::SetCurrent(init->thread);
+#if defined(WIN32)
+ SetThreadName(GetCurrentThreadId(), init->thread->name_.c_str());
+#elif defined(POSIX)
+ // TODO: See if naming exists for pthreads.
+#endif
+#ifdef OSX_USE_COCOA
+ // Make sure the new thread has an autoreleasepool
+ ScopedAutoreleasePool pool;
+#endif
+ if (init->runnable) {
+ init->runnable->Run(init->thread);
+ } else {
+ init->thread->Run();
+ }
+ delete init;
+ return NULL;
+}
+
+void Thread::Run() {
+ ProcessMessages(kForever);
+}
+
+bool Thread::IsOwned() {
+ return owned_;
+}
+
+void Thread::Stop() {
+ MessageQueue::Quit();
+ Join();
+}
+
+void Thread::Send(MessageHandler *phandler, uint32 id, MessageData *pdata) {
+ if (fStop_)
+ return;
+
+ // Sent messages are sent to the MessageHandler directly, in the context
+ // of "thread", like Win32 SendMessage. If in the right context,
+ // call the handler directly.
+
+ Message msg;
+ msg.phandler = phandler;
+ msg.message_id = id;
+ msg.pdata = pdata;
+ if (IsCurrent()) {
+ phandler->OnMessage(&msg);
+ return;
+ }
+
+ AutoThread thread;
+ Thread *current_thread = Thread::Current();
+ ASSERT(current_thread != NULL); // AutoThread ensures this
+
+ bool ready = false;
+ {
+ CritScope cs(&crit_);
+ EnsureActive();
+ _SendMessage smsg;
+ smsg.thread = current_thread;
+ smsg.msg = msg;
+ smsg.ready = &ready;
+ sendlist_.push_back(smsg);
+ has_sends_ = true;
+ }
+
+ // Wait for a reply
+
+ ss_->WakeUp();
+
+ bool waited = false;
+ while (!ready) {
+ current_thread->ReceiveSends();
+ current_thread->socketserver()->Wait(kForever, false);
+ waited = true;
+ }
+
+ // Our Wait loop above may have consumed some WakeUp events for this
+ // MessageQueue, that weren't relevant to this Send. Losing these WakeUps can
+ // cause problems for some SocketServers.
+ //
+ // Concrete example:
+ // Win32SocketServer on thread A calls Send on thread B. While processing the
+ // message, thread B Posts a message to A. We consume the wakeup for that
+ // Post while waiting for the Send to complete, which means that when we exit
+ // this loop, we need to issue another WakeUp, or else the Posted message
+ // won't be processed in a timely manner.
+
+ if (waited) {
+ current_thread->socketserver()->WakeUp();
+ }
+}
+
+void Thread::ReceiveSends() {
+ // Before entering critical section, check boolean.
+
+ if (!has_sends_)
+ return;
+
+ // Receive a sent message. Cleanup scenarios:
+ // - thread sending exits: We don't allow this, since thread can exit
+ // only via Join, so Send must complete.
+ // - thread receiving exits: Wakeup/set ready in Thread::Clear()
+ // - object target cleared: Wakeup/set ready in Thread::Clear()
+ crit_.Enter();
+ while (!sendlist_.empty()) {
+ _SendMessage smsg = sendlist_.front();
+ sendlist_.pop_front();
+ crit_.Leave();
+ smsg.msg.phandler->OnMessage(&smsg.msg);
+ crit_.Enter();
+ *smsg.ready = true;
+ smsg.thread->socketserver()->WakeUp();
+ }
+ has_sends_ = false;
+ crit_.Leave();
+}
+
+void Thread::Clear(MessageHandler *phandler, uint32 id,
+ MessageList* removed) {
+ CritScope cs(&crit_);
+
+ // Remove messages on sendlist_ with phandler
+ // Object target cleared: remove from send list, wakeup/set ready
+ // if sender not NULL.
+
+ std::list<_SendMessage>::iterator iter = sendlist_.begin();
+ while (iter != sendlist_.end()) {
+ _SendMessage smsg = *iter;
+ if (smsg.msg.Match(phandler, id)) {
+ if (removed) {
+ removed->push_back(smsg.msg);
+ } else {
+ delete smsg.msg.pdata;
+ }
+ iter = sendlist_.erase(iter);
+ *smsg.ready = true;
+ smsg.thread->socketserver()->WakeUp();
+ continue;
+ }
+ ++iter;
+ }
+
+ MessageQueue::Clear(phandler, id, removed);
+}
+
+bool Thread::ProcessMessages(int cmsLoop) {
+ uint32 msEnd = (kForever == cmsLoop) ? 0 : TimeAfter(cmsLoop);
+ int cmsNext = cmsLoop;
+
+ while (true) {
+ Message msg;
+ if (!Get(&msg, cmsNext))
+ return !IsQuitting();
+ Dispatch(&msg);
+
+ if (cmsLoop != kForever) {
+ cmsNext = TimeUntil(msEnd);
+ if (cmsNext < 0)
+ return true;
+ }
+ }
+}
+
+AutoThread::AutoThread(SocketServer* ss) : Thread(ss) {
+ if (!ThreadManager::CurrentThread()) {
+ ThreadManager::SetCurrent(this);
+ }
+}
+
+AutoThread::~AutoThread() {
+ if (ThreadManager::CurrentThread() == this) {
+ ThreadManager::SetCurrent(NULL);
+ }
+}
+
+#ifdef WIN32
+void ComThread::Run() {
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ ASSERT(SUCCEEDED(hr));
+ if (SUCCEEDED(hr)) {
+ Thread::Run();
+ CoUninitialize();
+ } else {
+ LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+ }
+}
+#endif
+
+} // namespace talk_base
diff --git a/talk/base/thread.h b/talk/base/thread.h
new file mode 100644
index 0000000..36b9e76
--- /dev/null
+++ b/talk/base/thread.h
@@ -0,0 +1,245 @@
+/*
+ * 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_THREAD_H_
+#define TALK_BASE_THREAD_H_
+
+#include <algorithm>
+#include <list>
+#include <string>
+#include <vector>
+
+#ifdef POSIX
+#include <pthread.h>
+#endif
+
+#include "talk/base/messagequeue.h"
+
+#ifdef WIN32
+#include "talk/base/win32.h"
+#endif
+
+namespace talk_base {
+
+class Thread;
+
+class ThreadManager {
+public:
+ ThreadManager();
+ ~ThreadManager();
+
+ static Thread *CurrentThread();
+ static void SetCurrent(Thread *thread);
+ void Add(Thread *thread);
+ void Remove(Thread *thread);
+
+ // Returns a thread object with its thread_ ivar set
+ // to whatever the OS uses to represent the thread.
+ // If there already *is* a Thread object corresponding to this thread,
+ // this method will return that. Otherwise it creates a new Thread
+ // object whose wrapped() method will return true, and whose
+ // handle will, on Win32, be opened with only synchronization privileges -
+ // if you need more privilegs, rather than changing this method, please
+ // write additional code to adjust the privileges, or call a different
+ // factory method of your own devising, because this one gets used in
+ // unexpected contexts (like inside browser plugins) and it would be a
+ // shame to break it. It is also conceivable on Win32 that we won't even
+ // be able to get synchronization privileges, in which case the result
+ // will have a NULL handle.
+ static Thread *WrapCurrentThread();
+ static void UnwrapCurrentThread();
+
+ static void StopAllThreads_(); // Experimental
+
+private:
+ Thread *main_thread_;
+ std::vector<Thread *> threads_;
+ CriticalSection crit_;
+
+#ifdef POSIX
+ static pthread_key_t key_;
+#endif
+
+#ifdef WIN32
+ static DWORD key_;
+#endif
+};
+
+class Thread;
+
+struct _SendMessage {
+ _SendMessage() {}
+ Thread *thread;
+ Message msg;
+ bool *ready;
+};
+
+enum ThreadPriority {
+ PRIORITY_HIGH,
+ PRIORITY_ABOVE_NORMAL,
+ PRIORITY_NORMAL,
+ PRIORITY_IDLE,
+};
+
+class Runnable {
+ public:
+ virtual ~Runnable() {}
+ virtual void Run(Thread* thread) = 0;
+};
+
+class Thread : public MessageQueue {
+public:
+ Thread(SocketServer* ss = NULL);
+ virtual ~Thread();
+
+ static inline Thread* Current() {
+ return ThreadManager::CurrentThread();
+ }
+
+ bool IsCurrent() const {
+ return (ThreadManager::CurrentThread() == this);
+ }
+
+ // Sleeps the calling thread for the specified number of milliseconds, during
+ // which time no processing is performed. Returns false if sleeping was
+ // interrupted by a signal (POSIX only).
+ static bool SleepMs(int millis);
+
+ // Sets the thread's name, for debugging. Must be called before Start().
+ // If |obj| is non-NULL, its value is appended to |name|.
+ const std::string& name() const { return name_; }
+ bool SetName(const std::string& name, const void* obj);
+
+ // Sets the thread's priority. Must be called before Start().
+ ThreadPriority priority() const { return priority_; }
+ bool SetPriority(ThreadPriority priority);
+
+ // Starts the execution of the thread.
+ bool started() const { return started_; }
+ bool Start(Runnable* runnable = NULL);
+
+ // Tells the thread to stop and waits until it is joined.
+ // Never call Stop on the current thread. Instead use the inherited Quit
+ // function which will exit the base MessageQueue without terminating the
+ // underlying OS thread.
+ virtual void Stop();
+
+ // By default, Thread::Run() calls ProcessMessages(kForever). To do other
+ // work, override Run(). To receive and dispatch messages, call
+ // ProcessMessages occasionally.
+ virtual void Run();
+
+ virtual void Send(MessageHandler *phandler, uint32 id = 0,
+ MessageData *pdata = NULL);
+
+ // From MessageQueue
+ virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY,
+ MessageList* removed = NULL);
+ virtual void ReceiveSends();
+
+ // ProcessMessages will process I/O and dispatch messages until:
+ // 1) cms milliseconds have elapsed (returns true)
+ // 2) Stop() is called (returns false)
+ bool ProcessMessages(int cms);
+
+ // Returns true if this is a thread that we created using the standard
+ // constructor, false if it was created by a call to
+ // ThreadManager::WrapCurrentThread(). The main thread of an application
+ // is generally not owned, since the OS representation of the thread
+ // obviously exists before we can get to it.
+ // You cannot call Start on non-owned threads.
+ bool IsOwned();
+
+#ifdef WIN32
+ HANDLE GetHandle() {
+ return thread_;
+ }
+#elif POSIX
+ pthread_t GetPThread() {
+ return thread_;
+ }
+#endif
+
+private:
+ static void *PreRun(void *pv);
+ // Blocks the calling thread until this thread has terminated.
+ void Join();
+
+ std::list<_SendMessage> sendlist_;
+ std::string name_;
+ ThreadPriority priority_;
+ bool started_;
+ bool has_sends_;
+
+#ifdef POSIX
+ pthread_t thread_;
+#endif
+
+#ifdef WIN32
+ HANDLE thread_;
+#endif
+
+ bool owned_;
+
+ friend class ThreadManager;
+};
+
+// AutoThread automatically installs itself at construction
+// uninstalls at destruction, if a Thread object is
+// _not already_ associated with the current OS thread.
+
+class AutoThread : public Thread {
+public:
+ AutoThread(SocketServer* ss = 0);
+ virtual ~AutoThread();
+};
+
+// Win32 extension for threads that need to use COM
+#ifdef WIN32
+class ComThread : public Thread {
+ protected:
+ virtual void Run();
+};
+#endif
+
+// Provides an easy way to install/uninstall a socketserver on a thread.
+class SocketServerScope {
+ public:
+ explicit SocketServerScope(SocketServer* ss) {
+ old_ss_ = Thread::Current()->socketserver();
+ Thread::Current()->set_socketserver(ss);
+ }
+ ~SocketServerScope() {
+ Thread::Current()->set_socketserver(old_ss_);
+ }
+ private:
+ SocketServer* old_ss_;
+};
+
+} // namespace talk_base
+
+#endif // TALK_BASE_THREAD_H_
diff --git a/talk/base/time.cc b/talk/base/time.cc
new file mode 100644
index 0000000..f5fa6db
--- /dev/null
+++ b/talk/base/time.cc
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <sys/time.h>
+#endif
+
+#ifdef WIN32
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#endif
+
+#include "talk/base/common.h"
+#include "talk/base/time.h"
+
+#define EFFICIENT_IMPLEMENTATION 1
+
+namespace talk_base {
+
+const uint32 LAST = 0xFFFFFFFF;
+const uint32 HALF = 0x80000000;
+
+#ifdef POSIX
+uint32 Time() {
+ struct timeval tv;
+ gettimeofday(&tv, 0);
+ return tv.tv_sec * 1000 + tv.tv_usec / 1000;
+}
+#endif
+
+#ifdef WIN32
+uint32 Time() {
+ return GetTickCount();
+}
+#endif
+
+uint32 StartTime() {
+ // Close to program execution time
+ static const uint32 g_start = Time();
+ return g_start;
+}
+
+// Make sure someone calls it so that it gets initialized
+static uint32 ignore = StartTime();
+
+uint32 TimeAfter(int32 elapsed) {
+ ASSERT(elapsed >= 0);
+ ASSERT(static_cast<uint32>(elapsed) < HALF);
+ return Time() + elapsed;
+}
+
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later) {
+ if (earlier <= later) {
+ return ((earlier <= middle) && (middle <= later));
+ } else {
+ return !((later < middle) && (middle < earlier));
+ }
+}
+
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+ int32 diff = later - earlier;
+ return (diff >= 0 && static_cast<uint32>(diff) < HALF);
+#else
+ const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+ return later_or_equal;
+#endif
+}
+
+bool TimeIsLater(uint32 earlier, uint32 later) {
+#if EFFICIENT_IMPLEMENTATION
+ int32 diff = later - earlier;
+ return (diff > 0 && static_cast<uint32>(diff) < HALF);
+#else
+ const bool earlier_or_equal = TimeIsBetween(later, earlier, later + HALF);
+ return !earlier_or_equal;
+#endif
+}
+
+int32 TimeDiff(uint32 later, uint32 earlier) {
+#if EFFICIENT_IMPLEMENTATION
+ return later - earlier;
+#else
+ const bool later_or_equal = TimeIsBetween(earlier, later, earlier + HALF);
+ if (later_or_equal) {
+ if (earlier <= later) {
+ return static_cast<long>(later - earlier);
+ } else {
+ return static_cast<long>(later + (LAST - earlier) + 1);
+ }
+ } else {
+ if (later <= earlier) {
+ return -static_cast<long>(earlier - later);
+ } else {
+ return -static_cast<long>(earlier + (LAST - later) + 1);
+ }
+ }
+#endif
+}
+
+} // namespace talk_base
diff --git a/talk/base/time.h b/talk/base/time.h
new file mode 100644
index 0000000..eca0e4e
--- /dev/null
+++ b/talk/base/time.h
@@ -0,0 +1,81 @@
+/*
+ * 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_TIME_H__
+#define TALK_BASE_TIME_H__
+
+#ifndef WIN32
+#include_next <time.h>
+#endif
+
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+typedef uint32 TimeStamp;
+
+// Returns the current time in milliseconds.
+uint32 Time();
+
+// Approximate time when the program started.
+uint32 StartTime();
+
+// Returns a future timestamp, 'elapsed' milliseconds from now.
+uint32 TimeAfter(int32 elapsed);
+
+// Comparisons between time values, which can wrap around.
+bool TimeIsBetween(uint32 earlier, uint32 middle, uint32 later); // Inclusive
+bool TimeIsLaterOrEqual(uint32 earlier, uint32 later); // Inclusive
+bool TimeIsLater(uint32 earlier, uint32 later); // Exclusive
+
+// Returns the later of two timestamps.
+inline uint32 TimeMax(uint32 ts1, uint32 ts2) {
+ return TimeIsLaterOrEqual(ts1, ts2) ? ts2 : ts1;
+}
+
+// Returns the earlier of two timestamps.
+inline uint32 TimeMin(uint32 ts1, uint32 ts2) {
+ return TimeIsLaterOrEqual(ts1, ts2) ? ts1 : ts2;
+}
+
+// Number of milliseconds that would elapse between 'earlier' and 'later'
+// timestamps. The value is negative if 'later' occurs before 'earlier'.
+int32 TimeDiff(uint32 later, uint32 earlier);
+
+// The number of milliseconds that have elapsed since 'earlier'.
+inline int32 TimeSince(uint32 earlier) {
+ return TimeDiff(Time(), earlier);
+}
+
+// The number of milliseconds that will elapse between now and 'later'.
+inline int32 TimeUntil(uint32 later) {
+ return TimeDiff(later, Time());
+}
+
+} // namespace talk_base
+
+#endif // TALK_BASE_TIME_H__
diff --git a/talk/base/unixfilesystem.cc b/talk/base/unixfilesystem.cc
new file mode 100644
index 0000000..436dc2f
--- /dev/null
+++ b/talk/base/unixfilesystem.cc
@@ -0,0 +1,522 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/unixfilesystem.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef OSX
+#include <Carbon/Carbon.h>
+#include <IOKit/IOCFBundle.h>
+#include <sys/statvfs.h>
+#include "talk/base/macutils.h"
+#endif // OSX
+
+#if defined(POSIX) && !defined(OSX)
+#include <sys/types.h>
+#ifdef ANDROID
+#include <sys/statfs.h>
+#else
+#include <sys/statvfs.h>
+#endif // ANDROID
+#include <pwd.h>
+#include <stdio.h>
+#include <unistd.h>
+#endif // POSIX && !OSX
+
+#ifdef LINUX
+#include <ctype.h>
+#include <algorithm>
+#endif
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+#ifdef ANDROID
+namespace {
+// Android does not have a concept of a single temp dir shared by all
+// because resource are scarse on a phone. Instead each app gets some
+// space on the sdcard under a path that is given at runtime by the
+// system.
+// The disk allocation feature is still work in progress so currently
+// we return a hardcoded a path on the sdcard. In the future, we
+// should do a JNI call to get that info from the context.
+// TODO: Replace hardcoded path with a query to the Context
+// object to get the equivalents of '/tmp' and '~/.'
+
+// @return the folder for libjingle. Some extra path (typically
+// Google/<app name>) will be added.
+const char* GetAndroidAppDataFolder() {
+ return "/sdcard";
+}
+
+// @return the tmp folder to be used. Some extra path will be added to
+// that base folder.
+const char* GetAndroidTempFolder() {
+ return "/sdcard";
+}
+
+} // anonymous namespace
+#endif
+
+namespace talk_base {
+
+std::string UnixFilesystem::app_temp_path_;
+
+bool UnixFilesystem::CreateFolder(const Pathname &path) {
+ std::string pathname(path.pathname());
+ int len = pathname.length();
+ if ((len == 0) || (pathname[len - 1] != '/'))
+ return false;
+
+ struct stat st;
+ int res = ::stat(pathname.c_str(), &st);
+ if (res == 0) {
+ // Something exists at this location, check if it is a directory
+ return S_ISDIR(st.st_mode) != 0;
+ } else if (errno != ENOENT) {
+ // Unexpected error
+ return false;
+ }
+
+ // Directory doesn't exist, look up one directory level
+ do {
+ --len;
+ } while ((len > 0) && (pathname[len - 1] != '/'));
+
+ if (!CreateFolder(Pathname(pathname.substr(0, len)))) {
+ return false;
+ }
+
+ LOG(LS_INFO) << "Creating folder: " << pathname;
+ return (0 == ::mkdir(pathname.c_str(), 0755));
+}
+
+FileStream *UnixFilesystem::OpenFile(const Pathname &filename,
+ const std::string &mode) {
+ FileStream *fs = new FileStream();
+ if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str())) {
+ delete fs;
+ fs = NULL;
+ }
+ return fs;
+}
+
+bool UnixFilesystem::CreatePrivateFile(const Pathname &filename) {
+ int fd = open(filename.pathname().c_str(),
+ O_RDWR | O_CREAT | O_EXCL,
+ S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ LOG_ERR(LS_ERROR) << "open() failed.";
+ return false;
+ }
+ // Don't need to keep the file descriptor.
+ if (close(fd) < 0) {
+ LOG_ERR(LS_ERROR) << "close() failed.";
+ // Continue.
+ }
+ return true;
+}
+
+bool UnixFilesystem::DeleteFile(const Pathname &filename) {
+ LOG(LS_INFO) << "Deleting file:" << filename.pathname();
+
+ if (!IsFile(filename)) {
+ ASSERT(IsFile(filename));
+ return false;
+ }
+ return ::unlink(filename.pathname().c_str()) == 0;
+}
+
+bool UnixFilesystem::DeleteEmptyFolder(const Pathname &folder) {
+ LOG(LS_INFO) << "Deleting folder" << folder.pathname();
+
+ if (!IsFolder(folder)) {
+ ASSERT(IsFolder(folder));
+ return false;
+ }
+ std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+ return ::rmdir(no_slash.c_str()) == 0;
+}
+
+bool UnixFilesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+ const std::string *append) {
+#ifdef OSX
+ FSRef fr;
+ if (0 != FSFindFolder(kOnAppropriateDisk, kTemporaryFolderType,
+ kCreateFolder, &fr))
+ return false;
+ unsigned char buffer[NAME_MAX+1];
+ if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ pathname.SetPathname(reinterpret_cast<char*>(buffer), "");
+#elif defined(ANDROID)
+ pathname.SetPathname(GetAndroidTempFolder(), "");
+#else // !OSX && !ANDROID
+ if (const char* tmpdir = getenv("TMPDIR")) {
+ pathname.SetPathname(tmpdir, "");
+ } else if (const char* tmp = getenv("TMP")) {
+ pathname.SetPathname(tmp, "");
+ } else {
+#ifdef P_tmpdir
+ pathname.SetPathname(P_tmpdir, "");
+#else // !P_tmpdir
+ pathname.SetPathname("/tmp/", "");
+#endif // !P_tmpdir
+ }
+#endif // !OSX && !ANDROID
+ if (append) {
+ ASSERT(!append->empty());
+ pathname.AppendFolder(*append);
+ }
+ return !create || CreateFolder(pathname);
+}
+
+std::string UnixFilesystem::TempFilename(const Pathname &dir,
+ const std::string &prefix) {
+ int len = dir.pathname().size() + prefix.size() + 2 + 6;
+ char *tempname = new char[len];
+
+ snprintf(tempname, len, "%s/%sXXXXXX", dir.pathname().c_str(),
+ prefix.c_str());
+ int fd = ::mkstemp(tempname);
+ if (fd != -1)
+ ::close(fd);
+ std::string ret(tempname);
+ delete[] tempname;
+
+ return ret;
+}
+
+bool UnixFilesystem::MoveFile(const Pathname &old_path,
+ const Pathname &new_path) {
+ if (!IsFile(old_path)) {
+ ASSERT(IsFile(old_path));
+ return false;
+ }
+ LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+ << " to " << new_path.pathname();
+ if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+ if (errno != EXDEV)
+ return false;
+ if (!CopyFile(old_path, new_path))
+ return false;
+ if (!DeleteFile(old_path))
+ return false;
+ }
+ return true;
+}
+
+bool UnixFilesystem::MoveFolder(const Pathname &old_path,
+ const Pathname &new_path) {
+ if (!IsFolder(old_path)) {
+ ASSERT(IsFolder(old_path));
+ return false;
+ }
+ LOG(LS_VERBOSE) << "Moving " << old_path.pathname()
+ << " to " << new_path.pathname();
+ if (rename(old_path.pathname().c_str(), new_path.pathname().c_str()) != 0) {
+ if (errno != EXDEV)
+ return false;
+ if (!CopyFolder(old_path, new_path))
+ return false;
+ if (!DeleteFolderAndContents(old_path))
+ return false;
+ }
+ return true;
+}
+
+bool UnixFilesystem::IsFolder(const Pathname &path) {
+ struct stat st;
+ if (stat(path.pathname().c_str(), &st) < 0)
+ return false;
+ return S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::CopyFile(const Pathname &old_path,
+ const Pathname &new_path) {
+ LOG(LS_VERBOSE) << "Copying " << old_path.pathname()
+ << " to " << new_path.pathname();
+ char buf[256];
+ size_t len;
+
+ StreamInterface *source = OpenFile(old_path, "rb");
+ if (!source)
+ return false;
+
+ StreamInterface *dest = OpenFile(new_path, "wb");
+ if (!dest) {
+ delete source;
+ return false;
+ }
+
+ while (source->Read(buf, sizeof(buf), &len, NULL) == SR_SUCCESS)
+ dest->Write(buf, len, NULL, NULL);
+
+ delete source;
+ delete dest;
+ return true;
+}
+
+bool UnixFilesystem::IsTemporaryPath(const Pathname& pathname) {
+ const char* const kTempPrefixes[] = {
+#ifdef ANDROID
+ GetAndroidTempFolder()
+#else
+ "/tmp/", "/var/tmp/",
+#ifdef OSX
+ "/private/tmp/", "/private/var/tmp/", "/private/var/folders/",
+#endif // OSX
+#endif // ANDROID
+ };
+ for (size_t i = 0; i < ARRAY_SIZE(kTempPrefixes); ++i) {
+ if (0 == strncmp(pathname.pathname().c_str(), kTempPrefixes[i],
+ strlen(kTempPrefixes[i])))
+ return true;
+ }
+ return false;
+}
+
+bool UnixFilesystem::IsFile(const Pathname& pathname) {
+ struct stat st;
+ int res = ::stat(pathname.pathname().c_str(), &st);
+ // Treat symlinks, named pipes, etc. all as files.
+ return res == 0 && !S_ISDIR(st.st_mode);
+}
+
+bool UnixFilesystem::IsAbsent(const Pathname& pathname) {
+ struct stat st;
+ int res = ::stat(pathname.pathname().c_str(), &st);
+ // Note: we specifically maintain ENOTDIR as an error, because that implies
+ // that you could not call CreateFolder(pathname).
+ return res != 0 && ENOENT == errno;
+}
+
+bool UnixFilesystem::GetFileSize(const Pathname& pathname, size_t *size) {
+ struct stat st;
+ if (::stat(pathname.pathname().c_str(), &st) != 0)
+ return false;
+ *size = st.st_size;
+ return true;
+}
+
+bool UnixFilesystem::GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time) {
+ struct stat st;
+ if (::stat(path.pathname().c_str(), &st) != 0)
+ return false;
+ switch (which) {
+ case FTT_CREATED:
+ *time = st.st_ctime;
+ break;
+ case FTT_MODIFIED:
+ *time = st.st_mtime;
+ break;
+ case FTT_ACCESSED:
+ *time = st.st_atime;
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool UnixFilesystem::GetAppPathname(Pathname* path) {
+#ifdef OSX
+ ProcessSerialNumber psn = { 0, kCurrentProcess };
+ CFDictionaryRef procinfo = ProcessInformationCopyDictionary(&psn,
+ kProcessDictionaryIncludeAllInformationMask);
+ if (NULL == procinfo)
+ return false;
+ CFStringRef cfpath = (CFStringRef) CFDictionaryGetValue(procinfo,
+ kIOBundleExecutableKey);
+ std::string path8;
+ bool success = ToUtf8(cfpath, &path8);
+ CFRelease(procinfo);
+ if (success)
+ path->SetPathname(path8);
+ return success;
+#else // OSX
+ char buffer[NAME_MAX+1];
+ size_t len = readlink("/proc/self/exe", buffer, ARRAY_SIZE(buffer) - 1);
+ if (len <= 0)
+ return false;
+ buffer[len] = '\0';
+ path->SetPathname(buffer);
+ return true;
+#endif // OSX
+}
+
+bool UnixFilesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+ ASSERT(!organization_name_.empty());
+ ASSERT(!application_name_.empty());
+
+ // First get the base directory for app data.
+#ifdef OSX
+ if (per_user) {
+ // Use ~/Library/Application Support/<orgname>/<appname>/
+ FSRef fr;
+ if (0 != FSFindFolder(kUserDomain, kApplicationSupportFolderType,
+ kCreateFolder, &fr))
+ return false;
+ unsigned char buffer[NAME_MAX+1];
+ if (0 != FSRefMakePath(&fr, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ path->SetPathname(reinterpret_cast<char*>(buffer), "");
+ } else {
+ // TODO
+ return false;
+ }
+#elif defined(ANDROID) // && !OSX
+ // TODO: Check if the new disk allocation mechanism works
+ // per-user and we don't have the per_user distinction.
+ path->SetPathname(GetAndroidAppDataFolder(), "");
+#elif defined(LINUX) // && !OSX && !defined(ANDROID)
+ if (per_user) {
+ // We follow the recommendations in
+ // http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ // It specifies separate directories for data and config files, but
+ // GetAppDataFolder() does not distinguish. We just return the config dir
+ // path.
+ const char* xdg_config_home = getenv("XDG_CONFIG_HOME");
+ if (xdg_config_home) {
+ path->SetPathname(xdg_config_home, "");
+ } else {
+ // XDG says to default to $HOME/.config. We also support falling back to
+ // other synonyms for HOME if for some reason it is not defined.
+ const char* homedir;
+ if (const char* home = getenv("HOME")) {
+ homedir = home;
+ } else if (const char* dotdir = getenv("DOTDIR")) {
+ homedir = dotdir;
+ } else if (passwd* pw = getpwuid(geteuid())) {
+ homedir = pw->pw_dir;
+ } else {
+ return false;
+ }
+ path->SetPathname(homedir, "");
+ path->AppendFolder(".config");
+ }
+ } else {
+ // XDG does not define a standard directory for writable global data. Let's
+ // just use this.
+ path->SetPathname("/var/cache/", "");
+ }
+#endif // !OSX && !defined(ANDROID) && !defined(LINUX)
+
+ // Now add on a sub-path for our app.
+#if defined(OSX) || defined(ANDROID)
+ path->AppendFolder(organization_name_);
+ path->AppendFolder(application_name_);
+#elif defined(LINUX)
+ // XDG says to use a single directory level, so we concatenate the org and app
+ // name with a hyphen. We also do the Linuxy thing and convert to all
+ // lowercase with no spaces.
+ std::string subdir(organization_name_);
+ subdir.append("-");
+ subdir.append(application_name_);
+ replace_substrs(" ", 1, "", 0, &subdir);
+ std::transform(subdir.begin(), subdir.end(), subdir.begin(), ::tolower);
+ path->AppendFolder(subdir);
+#endif
+ return CreateFolder(*path);
+}
+
+bool UnixFilesystem::GetAppTempFolder(Pathname* path) {
+ ASSERT(!application_name_.empty());
+ // TODO: Consider whether we are worried about thread safety.
+ if (!app_temp_path_.empty()) {
+ path->SetPathname(app_temp_path_);
+ return true;
+ }
+
+ // Create a random directory as /tmp/<appname>-<pid>-<timestamp>
+ char buffer[128];
+ sprintfn(buffer, ARRAY_SIZE(buffer), "-%d-%d",
+ static_cast<int>(getpid()),
+ static_cast<int>(time(0)));
+ std::string folder(application_name_);
+ folder.append(buffer);
+ if (!GetTemporaryFolder(*path, true, &folder))
+ return false;
+
+ app_temp_path_ = path->pathname();
+ // TODO: atexit(DeleteFolderAndContents(app_temp_path_));
+ return true;
+}
+
+bool UnixFilesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+ ASSERT(NULL != freebytes);
+ // TODO: Consider making relative paths absolute using cwd.
+ // TODO: When popping off a symlink, push back on the components of the
+ // symlink, so we don't jump out of the target disk inadvertently.
+ Pathname existing_path(path.folder(), "");
+ while (!existing_path.folder().empty() && IsAbsent(existing_path)) {
+ existing_path.SetFolder(existing_path.parent_folder());
+ }
+#ifdef ANDROID
+ struct statfs fs;
+ memset(&fs, 0, sizeof(fs));
+ if (0 != statfs(existing_path.pathname().c_str(), &fs))
+ return false;
+#else
+ struct statvfs vfs;
+ memset(&vfs, 0, sizeof(vfs));
+ if (0 != statvfs(existing_path.pathname().c_str(), &vfs))
+ return false;
+#endif // ANDROID
+#ifdef LINUX
+ *freebytes = static_cast<int64>(vfs.f_bsize) * vfs.f_bavail;
+#elif defined(OSX)
+ *freebytes = static_cast<int64>(vfs.f_frsize) * vfs.f_bavail;
+#elif defined(ANDROID)
+ *freebytes = static_cast<int64>(fs.f_bsize) * fs.f_bavail;
+#endif
+
+ return true;
+}
+
+Pathname UnixFilesystem::GetCurrentDirectory() {
+ Pathname cwd;
+ char buffer[PATH_MAX];
+ char *path = getcwd(buffer, PATH_MAX);
+
+ if (!path) {
+ LOG_ERR(LS_ERROR) << "getcwd() failed";
+ return cwd; // returns empty pathname
+ }
+ cwd.SetFolder(std::string(path));
+
+ return cwd;
+}
+
+} // namespace talk_base
diff --git a/talk/base/unixfilesystem.h b/talk/base/unixfilesystem.h
new file mode 100644
index 0000000..71960ac
--- /dev/null
+++ b/talk/base/unixfilesystem.h
@@ -0,0 +1,114 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_UNIXFILESYSTEM_H__
+#define _TALK_BASE_UNIXFILESYSTEM_H__
+
+#include "fileutils.h"
+
+namespace talk_base {
+
+class UnixFilesystem : public FilesystemInterface {
+ public:
+
+ // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise,
+ // returns NULL.
+ virtual FileStream *OpenFile(const Pathname &filename,
+ const std::string &mode);
+
+ // Atomically creates an empty file accessible only to the current user if one
+ // does not already exist at the given path, otherwise fails.
+ virtual bool CreatePrivateFile(const Pathname &filename);
+
+ // This will attempt to delete the file located at filename.
+ // It will fail with VERIY if you pass it a non-existant file, or a directory.
+ virtual bool DeleteFile(const Pathname &filename);
+
+ // This will attempt to delete the folder located at 'folder'
+ // It ASSERTs and returns false if you pass it a non-existant folder or a plain file.
+ virtual bool DeleteEmptyFolder(const Pathname &folder);
+
+ // Creates a directory. This will call itself recursively to create /foo/bar even if
+ // /foo does not exist.
+ // Returns TRUE if function succeeds
+ virtual bool CreateFolder(const Pathname &pathname);
+
+ // This moves a file from old_path to new_path, where "file" can be a plain file
+ // or directory, which will be moved recursively.
+ // Returns true if function succeeds.
+ virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+ virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+
+ // This copies a file from old_path to _new_path where "file" can be a plain file
+ // or directory, which will be copied recursively.
+ // Returns true if function succeeds
+ virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+ // Returns true if a pathname is a directory
+ virtual bool IsFolder(const Pathname& pathname);
+
+ // Returns true if pathname represents a temporary location on the system.
+ virtual bool IsTemporaryPath(const Pathname& pathname);
+
+ // Returns true of pathname represents an existing file
+ virtual bool IsFile(const Pathname& pathname);
+
+ // Returns true if pathname refers to no filesystem object, every parent
+ // directory either exists, or is also absent.
+ virtual bool IsAbsent(const Pathname& pathname);
+
+ virtual std::string TempFilename(const Pathname &dir, const std::string &prefix);
+
+ // A folder appropriate for storing temporary files (Contents are
+ // automatically deleted when the program exists)
+ virtual bool GetTemporaryFolder(Pathname &path, bool create,
+ const std::string *append);
+
+ virtual bool GetFileSize(const Pathname& path, size_t* size);
+ virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time);
+
+ // Returns the path to the running application.
+ virtual bool GetAppPathname(Pathname* path);
+
+ virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+ // Get a temporary folder that is unique to the current user and application.
+ virtual bool GetAppTempFolder(Pathname* path);
+
+ virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+ // Returns the absolute path of the current directory.
+ virtual Pathname GetCurrentDirectory();
+
+ private:
+ static std::string app_temp_path_;
+};
+
+} // namespace talk_base
+
+#endif // _UNIXFILESYSTEM_H__
diff --git a/talk/base/urlencode.cc b/talk/base/urlencode.cc
new file mode 100644
index 0000000..6dd51e1
--- /dev/null
+++ b/talk/base/urlencode.cc
@@ -0,0 +1,196 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/urlencode.h"
+
+#include "talk/base/common.h"
+#include "talk/base/stringutils.h"
+
+static int HexPairValue(const char * code) {
+ int value = 0;
+ const char * pch = code;
+ for (;;) {
+ int digit = *pch++;
+ if (digit >= '0' && digit <= '9') {
+ value += digit - '0';
+ }
+ else if (digit >= 'A' && digit <= 'F') {
+ value += digit - 'A' + 10;
+ }
+ else if (digit >= 'a' && digit <= 'f') {
+ value += digit - 'a' + 10;
+ }
+ else {
+ return -1;
+ }
+ if (pch == code + 2)
+ return value;
+ value <<= 4;
+ }
+}
+
+int InternalUrlDecode(const char *source, char *dest,
+ bool encode_space_as_plus) {
+ char * start = dest;
+
+ while (*source) {
+ switch (*source) {
+ case '+':
+ if (encode_space_as_plus) {
+ *(dest++) = ' ';
+ } else {
+ *dest++ = *source;
+ }
+ break;
+ case '%':
+ if (source[1] && source[2]) {
+ int value = HexPairValue(source + 1);
+ if (value >= 0) {
+ *(dest++) = value;
+ source += 2;
+ }
+ else {
+ *dest++ = '?';
+ }
+ }
+ else {
+ *dest++ = '?';
+ }
+ break;
+ default:
+ *dest++ = *source;
+ }
+ source++;
+ }
+
+ *dest = 0;
+ return dest - start;
+}
+
+int UrlDecode(const char *source, char *dest) {
+ return InternalUrlDecode(source, dest, true);
+}
+
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest) {
+ return InternalUrlDecode(source, dest, false);
+}
+
+bool IsValidUrlChar(char ch, bool unsafe_only) {
+ if (unsafe_only) {
+ return !(ch <= ' ' || strchr("\\\"^&`<>[]{}", ch));
+ } else {
+ return isalnum(ch) || strchr("-_.!~*'()", ch);
+ }
+}
+
+int InternalUrlEncode(const char *source, char *dest, unsigned int max,
+ bool encode_space_as_plus, bool unsafe_only) {
+ static const char *digits = "0123456789ABCDEF";
+ if (max == 0) {
+ return 0;
+ }
+
+ char *start = dest;
+ while (static_cast<unsigned>(dest - start) < max && *source) {
+ unsigned char ch = static_cast<unsigned char>(*source);
+ if (*source == ' ' && encode_space_as_plus && !unsafe_only) {
+ *dest++ = '+';
+ } else if (IsValidUrlChar(ch, unsafe_only)) {
+ *dest++ = *source;
+ } else {
+ if (static_cast<unsigned>(dest - start) + 4 > max) {
+ break;
+ }
+ *dest++ = '%';
+ *dest++ = digits[(ch >> 4) & 0x0F];
+ *dest++ = digits[ ch & 0x0F];
+ }
+ source++;
+ }
+ ASSERT(static_cast<unsigned int>(dest - start) < max);
+ *dest = 0;
+
+ return dest - start;
+}
+
+int UrlEncode(const char *source, char *dest, unsigned max) {
+ return InternalUrlEncode(source, dest, max, true, false);
+}
+
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+ unsigned max) {
+ return InternalUrlEncode(source, dest, max, false, false);
+}
+
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max) {
+ return InternalUrlEncode(source, dest, max, false, true);
+}
+
+std::string
+InternalUrlDecodeString(const std::string & encoded,
+ bool encode_space_as_plus) {
+ size_t needed_length = encoded.length() + 1;
+ char* buf = STACK_ARRAY(char, needed_length);
+ InternalUrlDecode(encoded.c_str(), buf, encode_space_as_plus);
+ return buf;
+}
+
+std::string
+UrlDecodeString(const std::string & encoded) {
+ return InternalUrlDecodeString(encoded, true);
+}
+
+std::string
+UrlDecodeStringWithoutEncodingSpaceAsPlus(const std::string & encoded) {
+ return InternalUrlDecodeString(encoded, false);
+}
+
+std::string
+InternalUrlEncodeString(const std::string & decoded,
+ bool encode_space_as_plus,
+ bool unsafe_only) {
+ size_t needed_length = decoded.length() * 3 + 1;
+ char* buf = STACK_ARRAY(char, needed_length);
+ InternalUrlEncode(decoded.c_str(), buf, needed_length,
+ encode_space_as_plus, unsafe_only);
+ return buf;
+}
+
+std::string
+UrlEncodeString(const std::string & decoded) {
+ return InternalUrlEncodeString(decoded, true, false);
+}
+
+std::string
+UrlEncodeStringWithoutEncodingSpaceAsPlus(const std::string & decoded) {
+ return InternalUrlEncodeString(decoded, false, false);
+}
+
+std::string
+UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded) {
+ return InternalUrlEncodeString(decoded, false, true);
+}
diff --git a/talk/base/urlencode.h b/talk/base/urlencode.h
new file mode 100644
index 0000000..05165e8
--- /dev/null
+++ b/talk/base/urlencode.h
@@ -0,0 +1,60 @@
+/*
+ * libjingle
+ * Copyright 2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _URLENCODE_H_
+#define _URLENCODE_H_
+
+#include <string>
+
+// Decode all encoded characters. Also decode + as space.
+int UrlDecode(const char *source, char *dest);
+
+// Decode all encoded characters.
+int UrlDecodeWithoutEncodingSpaceAsPlus(const char *source, char *dest);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+// Also encode space as +.
+int UrlEncode(const char *source, char *dest, unsigned max);
+
+// Encode all characters except alphas, numbers, and -_.!~*'()
+int UrlEncodeWithoutEncodingSpaceAsPlus(const char *source, char *dest,
+ unsigned max);
+
+// Encode only unsafe chars, including \ "^&`<>[]{}
+// Also encode space as %20, instead of +
+int UrlEncodeOnlyUnsafeChars(const char *source, char *dest, unsigned max);
+
+std::string UrlDecodeString(const std::string & encoded);
+std::string UrlDecodeStringWithoutEncodingSpaceAsPlus(
+ const std::string & encoded);
+std::string UrlEncodeString(const std::string & decoded);
+std::string UrlEncodeStringWithoutEncodingSpaceAsPlus(
+ const std::string & decoded);
+std::string UrlEncodeStringForOnlyUnsafeChars(const std::string & decoded);
+
+#endif
+
diff --git a/talk/base/win32.cc b/talk/base/win32.cc
new file mode 100644
index 0000000..9a816c4
--- /dev/null
+++ b/talk/base/win32.cc
@@ -0,0 +1,183 @@
+/*
+ * 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 "talk/base/win32.h"
+#include <algorithm>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+
+namespace talk_base {
+
+//
+// Unix time is in seconds relative to 1/1/1970. So we compute the windows
+// FILETIME of that time/date, then we add/subtract in appropriate units to
+// convert to/from unix time.
+// The units of FILETIME are 100ns intervals, so by multiplying by or dividing
+// by 10000000, we can convert to/from seconds.
+//
+// FileTime = UnixTime*10000000 + FileTime(1970)
+// UnixTime = (FileTime-FileTime(1970))/10000000
+//
+
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut) {
+ ASSERT(NULL != ut);
+
+ // FILETIME has an earlier date base than time_t (1/1/1970), so subtract off
+ // the difference.
+ SYSTEMTIME base_st;
+ memset(&base_st, 0, sizeof(base_st));
+ base_st.wDay = 1;
+ base_st.wMonth = 1;
+ base_st.wYear = 1970;
+
+ FILETIME base_ft;
+ SystemTimeToFileTime(&base_st, &base_ft);
+
+ ULARGE_INTEGER base_ul, current_ul;
+ memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+ memcpy(¤t_ul, &ft, sizeof(FILETIME));
+
+ // Divide by big number to convert to seconds, then subtract out the 1970
+ // base date value.
+ const ULONGLONG RATIO = 10000000;
+ *ut = static_cast<time_t>((current_ul.QuadPart - base_ul.QuadPart) / RATIO);
+}
+
+void UnixTimeToFileTime(const time_t& ut, FILETIME* ft) {
+ ASSERT(NULL != ft);
+
+ // FILETIME has an earlier date base than time_t (1/1/1970), so add in
+ // the difference.
+ SYSTEMTIME base_st;
+ memset(&base_st, 0, sizeof(base_st));
+ base_st.wDay = 1;
+ base_st.wMonth = 1;
+ base_st.wYear = 1970;
+
+ FILETIME base_ft;
+ SystemTimeToFileTime(&base_st, &base_ft);
+
+ ULARGE_INTEGER base_ul;
+ memcpy(&base_ul, &base_ft, sizeof(FILETIME));
+
+ // Multiply by big number to convert to 100ns units, then add in the 1970
+ // base date value.
+ const ULONGLONG RATIO = 10000000;
+ ULARGE_INTEGER current_ul;
+ current_ul.QuadPart = base_ul.QuadPart + static_cast<int64>(ut) * RATIO;
+ memcpy(ft, ¤t_ul, sizeof(FILETIME));
+}
+
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename) {
+ // TODO: Integrate into fileutils.h
+ // TODO: Handle wide and non-wide cases via TCHAR?
+ // TODO: Skip \\?\ processing if the length is not > MAX_PATH?
+ // TODO: Write unittests
+
+ // Convert to Utf16
+ int wlen = ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.length() + 1,
+ NULL, 0);
+ if (0 == wlen) {
+ return false;
+ }
+ wchar_t* wfilename = STACK_ARRAY(wchar_t, wlen);
+ if (0 == ::MultiByteToWideChar(CP_UTF8, 0, utf8.c_str(), utf8.length() + 1,
+ wfilename, wlen)) {
+ return false;
+ }
+ // Replace forward slashes with backslashes
+ std::replace(wfilename, wfilename + wlen, L'/', L'\\');
+ // Convert to complete filename
+ DWORD full_len = ::GetFullPathName(wfilename, 0, NULL, NULL);
+ if (0 == full_len) {
+ return false;
+ }
+ wchar_t* filepart = NULL;
+ wchar_t* full_filename = STACK_ARRAY(wchar_t, full_len + 6);
+ wchar_t* start = full_filename + 6;
+ if (0 == ::GetFullPathName(wfilename, full_len, start, &filepart)) {
+ return false;
+ }
+ // Add long-path prefix
+ const wchar_t kLongPathPrefix[] = L"\\\\?\\UNC";
+ if ((start[0] != L'\\') || (start[1] != L'\\')) {
+ // Non-unc path: <pathname>
+ // Becomes: \\?\<pathname>
+ start -= 4;
+ ASSERT(start >= full_filename);
+ memcpy(start, kLongPathPrefix, 4 * sizeof(wchar_t));
+ } else if (start[2] != L'?') {
+ // Unc path: \\<server>\<pathname>
+ // Becomes: \\?\UNC\<server>\<pathname>
+ start -= 6;
+ ASSERT(start >= full_filename);
+ memcpy(start, kLongPathPrefix, 7 * sizeof(wchar_t));
+ } else {
+ // Already in long-path form.
+ }
+ filename->assign(start);
+ return true;
+}
+
+bool GetOsVersion(int* major, int* minor, int* build) {
+ OSVERSIONINFO info = {0};
+ info.dwOSVersionInfoSize = sizeof(info);
+ if (GetVersionEx(&info)) {
+ if (major) *major = info.dwMajorVersion;
+ if (minor) *minor = info.dwMinorVersion;
+ if (build) *build = info.dwBuildNumber;
+ return true;
+ }
+ return false;
+}
+
+bool GetCurrentProcessIntegrityLevel(int* level) {
+ bool ret = false;
+ HANDLE process = GetCurrentProcess(), token;
+ if (OpenProcessToken(process, TOKEN_QUERY | TOKEN_QUERY_SOURCE, &token)) {
+ DWORD size;
+ if (!GetTokenInformation(token, TokenIntegrityLevel, NULL, 0, &size) &&
+ GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+
+ char* buf = STACK_ARRAY(char, size);
+ TOKEN_MANDATORY_LABEL* til =
+ reinterpret_cast<TOKEN_MANDATORY_LABEL*>(buf);
+ if (GetTokenInformation(token, TokenIntegrityLevel, til, size, &size)) {
+
+ DWORD count = *GetSidSubAuthorityCount(til->Label.Sid);
+ *level = *GetSidSubAuthority(til->Label.Sid, count - 1);
+ ret = true;
+ }
+ }
+ CloseHandle(token);
+ }
+ return ret;
+}
+
+} // namespace talk_base
+
diff --git a/talk/base/win32.h b/talk/base/win32.h
new file mode 100644
index 0000000..6274b0e
--- /dev/null
+++ b/talk/base/win32.h
@@ -0,0 +1,130 @@
+/*
+ * 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_WIN32_H_
+#define TALK_BASE_WIN32_H_
+
+#ifdef WIN32
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include <winsock2.h>
+#include <windows.h>
+
+#ifndef SECURITY_MANDATORY_LABEL_AUTHORITY
+// Add defines that we use if we are compiling against older sdks
+#define SECURITY_MANDATORY_MEDIUM_RID (0x00002000L)
+#define TokenIntegrityLevel static_cast<TOKEN_INFORMATION_CLASS>(0x19)
+typedef struct _TOKEN_MANDATORY_LABEL {
+ SID_AND_ATTRIBUTES Label;
+} TOKEN_MANDATORY_LABEL, *PTOKEN_MANDATORY_LABEL;
+#endif // SECURITY_MANDATORY_LABEL_AUTHORITY
+
+#undef SetPort
+
+#include <string>
+
+#include "talk/base/stringutils.h"
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+inline std::wstring ToUtf16(const char* utf8, size_t len) {
+ int len16 = ::MultiByteToWideChar(CP_UTF8, 0, utf8, len, NULL, 0);
+ wchar_t* ws = STACK_ARRAY(wchar_t, len16);
+ ::MultiByteToWideChar(CP_UTF8, 0, utf8, len, ws, len16);
+ return std::wstring(ws, len16);
+}
+
+inline std::wstring ToUtf16(const std::string& str) {
+ return ToUtf16(str.data(), str.length());
+}
+
+inline std::string ToUtf8(const wchar_t* wide, size_t len) {
+ int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wide, len, NULL, 0, NULL, NULL);
+ char* ns = STACK_ARRAY(char, len8);
+ ::WideCharToMultiByte(CP_UTF8, 0, wide, len, ns, len8, NULL, NULL);
+ return std::string(ns, len8);
+}
+
+inline std::string ToUtf8(const std::wstring& wstr) {
+ return ToUtf8(wstr.data(), wstr.length());
+}
+
+// Convert FILETIME to time_t
+void FileTimeToUnixTime(const FILETIME& ft, time_t* ut);
+
+// Convert time_t to FILETIME
+void UnixTimeToFileTime(const time_t& ut, FILETIME * ft);
+
+// Convert a Utf8 path representation to a non-length-limited Unicode pathname.
+bool Utf8ToWindowsFilename(const std::string& utf8, std::wstring* filename);
+
+// Convert a FILETIME to a UInt64
+inline uint64 ToUInt64(const FILETIME& ft) {
+ ULARGE_INTEGER r = {ft.dwLowDateTime, ft.dwHighDateTime};
+ return r.QuadPart;
+}
+
+enum WindowsMajorVersions {
+ kWindows2000 = 5,
+ kWindowsVista = 6,
+};
+bool GetOsVersion(int* major, int* minor, int* build);
+
+inline bool IsWindowsVistaOrLater() {
+ int major;
+ return (GetOsVersion(&major, NULL, NULL) && major >= kWindowsVista);
+}
+
+inline bool IsWindowsXpOrLater() {
+ int major, minor;
+ return (GetOsVersion(&major, &minor, NULL) &&
+ (major >= kWindowsVista ||
+ (major == kWindows2000 && minor >= 1)));
+}
+
+// Determine the current integrity level of the process.
+bool GetCurrentProcessIntegrityLevel(int* level);
+
+inline bool IsCurrentProcessLowIntegrity() {
+ int level;
+ return (GetCurrentProcessIntegrityLevel(&level) &&
+ level < SECURITY_MANDATORY_MEDIUM_RID);
+}
+
+bool AdjustCurrentProcessPrivilege(const TCHAR* privilege, bool to_enable);
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // WIN32
+#endif // TALK_BASE_WIN32_H_
diff --git a/talk/base/win32filesystem.cc b/talk/base/win32filesystem.cc
new file mode 100644
index 0000000..f2a2256
--- /dev/null
+++ b/talk/base/win32filesystem.cc
@@ -0,0 +1,477 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/win32filesystem.h"
+
+#include "talk/base/win32.h"
+#include <shellapi.h>
+#include <shlobj.h>
+#include <tchar.h>
+
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stream.h"
+#include "talk/base/stringutils.h"
+
+// In several places in this file, we test the integrity level of the process
+// before calling GetLongPathName. We do this because calling GetLongPathName
+// when running under protected mode IE (a low integrity process) can result in
+// a virtualized path being returned, which is wrong if you only plan to read.
+// TODO: Waiting to hear back from IE team on whether this is the
+// best approach; IEIsProtectedModeProcess is another possible solution.
+
+namespace talk_base {
+
+bool Win32Filesystem::CreateFolder(const Pathname &pathname) {
+ if (pathname.pathname().empty() || !pathname.filename().empty())
+ return false;
+
+ std::wstring path16;
+ if (!Utf8ToWindowsFilename(pathname.pathname(), &path16))
+ return false;
+
+ DWORD res = ::GetFileAttributes(path16.c_str());
+ if (res != INVALID_FILE_ATTRIBUTES) {
+ // Something exists at this location, check if it is a directory
+ return ((res & FILE_ATTRIBUTE_DIRECTORY) != 0);
+ } else if ((GetLastError() != ERROR_FILE_NOT_FOUND)
+ && (GetLastError() != ERROR_PATH_NOT_FOUND)) {
+ // Unexpected error
+ return false;
+ }
+
+ // Directory doesn't exist, look up one directory level
+ if (!pathname.parent_folder().empty()) {
+ Pathname parent(pathname);
+ parent.SetFolder(pathname.parent_folder());
+ if (!CreateFolder(parent)) {
+ return false;
+ }
+ }
+
+ return (::CreateDirectory(path16.c_str(), NULL) != 0);
+}
+
+FileStream *Win32Filesystem::OpenFile(const Pathname &filename,
+ const std::string &mode) {
+ FileStream *fs = new FileStream();
+ if (fs && !fs->Open(filename.pathname().c_str(), mode.c_str())) {
+ delete fs;
+ fs = NULL;
+ }
+ return fs;
+}
+
+bool Win32Filesystem::CreatePrivateFile(const Pathname &filename) {
+ // To make the file private to the current user, we first must construct a
+ // SECURITY_DESCRIPTOR specifying an ACL. This code is mostly based upon
+ // http://msdn.microsoft.com/en-us/library/ms707085%28VS.85%29.aspx
+
+ // Get the current process token.
+ HANDLE process_token = INVALID_HANDLE_VALUE;
+ if (!::OpenProcessToken(GetCurrentProcess(),
+ TOKEN_QUERY,
+ &process_token)) {
+ LOG_ERR(LS_ERROR) << "OpenProcessToken() failed";
+ return false;
+ }
+
+ // Get the size of its TOKEN_USER structure. Return value is not checked
+ // because we expect it to fail.
+ DWORD token_user_size = 0;
+ (void)::GetTokenInformation(process_token,
+ TokenUser,
+ NULL,
+ 0,
+ &token_user_size);
+
+ // Get the TOKEN_USER structure.
+ scoped_array<char> token_user_bytes(new char[token_user_size]);
+ PTOKEN_USER token_user = reinterpret_cast<PTOKEN_USER>(
+ token_user_bytes.get());
+ memset(token_user, 0, token_user_size);
+ BOOL success = ::GetTokenInformation(process_token,
+ TokenUser,
+ token_user,
+ token_user_size,
+ &token_user_size);
+ // We're now done with this.
+ ::CloseHandle(process_token);
+ if (!success) {
+ LOG_ERR(LS_ERROR) << "GetTokenInformation() failed";
+ return false;
+ }
+
+ if (!IsValidSid(token_user->User.Sid)) {
+ LOG_ERR(LS_ERROR) << "Current process has invalid user SID";
+ return false;
+ }
+
+ // Compute size needed for an ACL that allows access to just this user.
+ int acl_size = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +
+ GetLengthSid(token_user->User.Sid);
+
+ // Allocate it.
+ scoped_array<char> acl_bytes(new char[acl_size]);
+ PACL acl = reinterpret_cast<PACL>(acl_bytes.get());
+ memset(acl, 0, acl_size);
+ if (!::InitializeAcl(acl, acl_size, ACL_REVISION)) {
+ LOG_ERR(LS_ERROR) << "InitializeAcl() failed";
+ return false;
+ }
+
+ // Allow access to only the current user.
+ if (!::AddAccessAllowedAce(acl,
+ ACL_REVISION,
+ GENERIC_READ | GENERIC_WRITE | STANDARD_RIGHTS_ALL,
+ token_user->User.Sid)) {
+ LOG_ERR(LS_ERROR) << "AddAccessAllowedAce() failed";
+ return false;
+ }
+
+ // Now make the security descriptor.
+ SECURITY_DESCRIPTOR security_descriptor;
+ if (!::InitializeSecurityDescriptor(&security_descriptor,
+ SECURITY_DESCRIPTOR_REVISION)) {
+ LOG_ERR(LS_ERROR) << "InitializeSecurityDescriptor() failed";
+ return false;
+ }
+
+ // Put the ACL in it.
+ if (!::SetSecurityDescriptorDacl(&security_descriptor,
+ TRUE,
+ acl,
+ FALSE)) {
+ LOG_ERR(LS_ERROR) << "SetSecurityDescriptorDacl() failed";
+ return false;
+ }
+
+ // Finally create the file.
+ SECURITY_ATTRIBUTES security_attributes;
+ security_attributes.nLength = sizeof(security_attributes);
+ security_attributes.lpSecurityDescriptor = &security_descriptor;
+ security_attributes.bInheritHandle = FALSE;
+ HANDLE handle = ::CreateFile(
+ ToUtf16(filename.pathname()).c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
+ &security_attributes,
+ CREATE_NEW,
+ 0,
+ NULL);
+ if (INVALID_HANDLE_VALUE == handle) {
+ LOG_ERR(LS_ERROR) << "CreateFile() failed";
+ return false;
+ }
+ if (!::CloseHandle(handle)) {
+ LOG_ERR(LS_ERROR) << "CloseFile() failed";
+ // Continue.
+ }
+ return true;
+}
+
+bool Win32Filesystem::DeleteFile(const Pathname &filename) {
+ LOG(LS_INFO) << "Deleting file " << filename.pathname();
+ if (!IsFile(filename)) {
+ ASSERT(IsFile(filename));
+ return false;
+ }
+ return ::DeleteFile(ToUtf16(filename.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::DeleteEmptyFolder(const Pathname &folder) {
+ LOG(LS_INFO) << "Deleting folder " << folder.pathname();
+
+ std::string no_slash(folder.pathname(), 0, folder.pathname().length()-1);
+ return ::RemoveDirectory(ToUtf16(no_slash).c_str()) != 0;
+}
+
+bool Win32Filesystem::GetTemporaryFolder(Pathname &pathname, bool create,
+ const std::string *append) {
+ wchar_t buffer[MAX_PATH + 1];
+ if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+ return false;
+ if (!IsCurrentProcessLowIntegrity() &&
+ !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ size_t len = strlen(buffer);
+ if ((len > 0) && (buffer[len-1] != '\\')) {
+ len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, L"\\");
+ }
+ if (len >= ARRAY_SIZE(buffer) - 1)
+ return false;
+ pathname.clear();
+ pathname.SetFolder(ToUtf8(buffer));
+ if (append != NULL) {
+ ASSERT(!append->empty());
+ pathname.AppendFolder(*append);
+ }
+ return !create || CreateFolder(pathname);
+}
+
+std::string Win32Filesystem::TempFilename(const Pathname &dir,
+ const std::string &prefix) {
+ wchar_t filename[MAX_PATH];
+ if (::GetTempFileName(ToUtf16(dir.pathname()).c_str(),
+ ToUtf16(prefix).c_str(), 0, filename) != 0)
+ return ToUtf8(filename);
+ ASSERT(false);
+ return "";
+}
+
+bool Win32Filesystem::MoveFile(const Pathname &old_path,
+ const Pathname &new_path) {
+ if (!IsFile(old_path)) {
+ ASSERT(IsFile(old_path));
+ return false;
+ }
+ LOG(LS_INFO) << "Moving " << old_path.pathname()
+ << " to " << new_path.pathname();
+ return ::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+ ToUtf16(new_path.pathname()).c_str()) != 0;
+}
+
+bool Win32Filesystem::MoveFolder(const Pathname &old_path,
+ const Pathname &new_path) {
+ if (!IsFolder(old_path)) {
+ ASSERT(IsFolder(old_path));
+ return false;
+ }
+ LOG(LS_INFO) << "Moving " << old_path.pathname()
+ << " to " << new_path.pathname();
+ if (::MoveFile(ToUtf16(old_path.pathname()).c_str(),
+ ToUtf16(new_path.pathname()).c_str()) == 0) {
+ if (::GetLastError() != ERROR_NOT_SAME_DEVICE) {
+ LOG_GLE(LS_ERROR) << "Failed to move file";
+ return false;
+ }
+ if (!CopyFolder(old_path, new_path))
+ return false;
+ if (!DeleteFolderAndContents(old_path))
+ return false;
+ }
+ return true;
+}
+
+bool Win32Filesystem::IsFolder(const Pathname &path) {
+ WIN32_FILE_ATTRIBUTE_DATA data = {0};
+ if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+ GetFileExInfoStandard, &data))
+ return false;
+ return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ==
+ FILE_ATTRIBUTE_DIRECTORY;
+}
+
+bool Win32Filesystem::IsFile(const Pathname &path) {
+ WIN32_FILE_ATTRIBUTE_DATA data = {0};
+ if (0 == ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+ GetFileExInfoStandard, &data))
+ return false;
+ return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0;
+}
+
+bool Win32Filesystem::IsAbsent(const Pathname& path) {
+ WIN32_FILE_ATTRIBUTE_DATA data = {0};
+ if (0 != ::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+ GetFileExInfoStandard, &data))
+ return false;
+ DWORD err = ::GetLastError();
+ return (ERROR_FILE_NOT_FOUND == err || ERROR_PATH_NOT_FOUND == err);
+}
+
+bool Win32Filesystem::CopyFile(const Pathname &old_path,
+ const Pathname &new_path) {
+ return ::CopyFile(ToUtf16(old_path.pathname()).c_str(),
+ ToUtf16(new_path.pathname()).c_str(), TRUE) != 0;
+}
+
+bool Win32Filesystem::IsTemporaryPath(const Pathname& pathname) {
+ TCHAR buffer[MAX_PATH + 1];
+ if (!::GetTempPath(ARRAY_SIZE(buffer), buffer))
+ return false;
+ if (!IsCurrentProcessLowIntegrity() &&
+ !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ return (::strnicmp(ToUtf16(pathname.pathname()).c_str(),
+ buffer, strlen(buffer)) == 0);
+}
+
+bool Win32Filesystem::GetFileSize(const Pathname &pathname, size_t *size) {
+ WIN32_FILE_ATTRIBUTE_DATA data = {0};
+ if (::GetFileAttributesEx(ToUtf16(pathname.pathname()).c_str(),
+ GetFileExInfoStandard, &data) == 0)
+ return false;
+ *size = data.nFileSizeLow;
+ return true;
+}
+
+bool Win32Filesystem::GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time) {
+ WIN32_FILE_ATTRIBUTE_DATA data = {0};
+ if (::GetFileAttributesEx(ToUtf16(path.pathname()).c_str(),
+ GetFileExInfoStandard, &data) == 0)
+ return false;
+ switch (which) {
+ case FTT_CREATED:
+ FileTimeToUnixTime(data.ftCreationTime, time);
+ break;
+ case FTT_MODIFIED:
+ FileTimeToUnixTime(data.ftLastWriteTime, time);
+ break;
+ case FTT_ACCESSED:
+ FileTimeToUnixTime(data.ftLastAccessTime, time);
+ break;
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool Win32Filesystem::GetAppPathname(Pathname* path) {
+ TCHAR buffer[MAX_PATH + 1];
+ if (0 == ::GetModuleFileName(NULL, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ path->SetPathname(ToUtf8(buffer));
+ return true;
+}
+
+bool Win32Filesystem::GetAppDataFolder(Pathname* path, bool per_user) {
+ ASSERT(!organization_name_.empty());
+ ASSERT(!application_name_.empty());
+ TCHAR buffer[MAX_PATH + 1];
+ int csidl = per_user ? CSIDL_LOCAL_APPDATA : CSIDL_COMMON_APPDATA;
+ if (!::SHGetSpecialFolderPath(NULL, buffer, csidl, TRUE))
+ return false;
+ if (!IsCurrentProcessLowIntegrity() &&
+ !::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer)))
+ return false;
+ size_t len = strcatn(buffer, ARRAY_SIZE(buffer), __T("\\"));
+ len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+ ToUtf16(organization_name_).c_str());
+ if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+ len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+ }
+ len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len,
+ ToUtf16(application_name_).c_str());
+ if ((len > 0) && (buffer[len-1] != __T('\\'))) {
+ len += strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, __T("\\"));
+ }
+ if (len >= ARRAY_SIZE(buffer) - 1)
+ return false;
+ path->clear();
+ path->SetFolder(ToUtf8(buffer));
+ return CreateFolder(*path);
+}
+
+bool Win32Filesystem::GetAppTempFolder(Pathname* path) {
+ if (!GetAppPathname(path))
+ return false;
+ std::string filename(path->filename());
+ return GetTemporaryFolder(*path, true, &filename);
+}
+
+bool Win32Filesystem::GetDiskFreeSpace(const Pathname& path, int64 *freebytes) {
+ if (!freebytes) {
+ return false;
+ }
+ char drive[4];
+ std::wstring drive16;
+ const wchar_t* target_drive = NULL;
+ if (path.GetDrive(drive, sizeof(drive))) {
+ drive16 = ToUtf16(drive);
+ target_drive = drive16.c_str();
+ } else if (path.folder().substr(0, 2) == "\\\\") {
+ // UNC path, fail.
+ // TODO: Handle UNC paths.
+ return false;
+ } else {
+ // The path is probably relative. GetDriveType and GetDiskFreeSpaceEx
+ // use the current drive if NULL is passed as the drive name.
+ // TODO: Add method to Pathname to determine if the path is relative.
+ // TODO: Add method to Pathname to convert a path to absolute.
+ }
+ UINT driveType = ::GetDriveType(target_drive);
+ if ( (driveType & DRIVE_REMOTE) || (driveType & DRIVE_UNKNOWN) ) {
+ LOG(LS_VERBOSE) << " remove or unknown drive " << drive;
+ return false;
+ }
+
+ int64 totalNumberOfBytes; // receives the number of bytes on disk
+ int64 totalNumberOfFreeBytes; // receives the free bytes on disk
+ // make sure things won't change in 64 bit machine
+ // TODO replace with compile time assert
+ ASSERT(sizeof(ULARGE_INTEGER) == sizeof(uint64)); //NOLINT
+ if (::GetDiskFreeSpaceEx(target_drive,
+ (PULARGE_INTEGER)freebytes,
+ (PULARGE_INTEGER)&totalNumberOfBytes,
+ (PULARGE_INTEGER)&totalNumberOfFreeBytes)) {
+ return true;
+ } else {
+ LOG(LS_VERBOSE) << " GetDiskFreeSpaceEx returns error ";
+ return false;
+ }
+}
+
+Pathname Win32Filesystem::GetCurrentDirectory() {
+ Pathname cwd;
+ int path_len = 0;
+ scoped_array<wchar_t> path;
+ do {
+ int needed = ::GetCurrentDirectory(path_len, path.get());
+ if (needed == 0) {
+ // Error.
+ LOG_GLE(LS_ERROR) << "::GetCurrentDirectory() failed";
+ return cwd; // returns empty pathname
+ }
+ if (needed <= path_len) {
+ // It wrote successfully.
+ break;
+ }
+ // Else need to re-alloc for "needed".
+ path.reset(new wchar_t[needed]);
+ path_len = needed;
+ } while (true);
+ cwd.SetFolder(ToUtf8(path.get()));
+ return cwd;
+}
+
+// TODO: Consider overriding DeleteFolderAndContents for speed and potentially
+// better OS integration (recycle bin?)
+/*
+ std::wstring temp_path16 = ToUtf16(temp_path.pathname());
+ temp_path16.append(1, '*');
+ temp_path16.append(1, '\0');
+
+ SHFILEOPSTRUCT file_op = { 0 };
+ file_op.wFunc = FO_DELETE;
+ file_op.pFrom = temp_path16.c_str();
+ file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT;
+ return (0 == SHFileOperation(&file_op));
+*/
+
+} // namespace talk_base
diff --git a/talk/base/win32filesystem.h b/talk/base/win32filesystem.h
new file mode 100644
index 0000000..c17bdd9
--- /dev/null
+++ b/talk/base/win32filesystem.h
@@ -0,0 +1,118 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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_WIN32FILESYSTEM_H__
+#define _TALK_BASE_WIN32FILESYSTEM_H__
+
+#include "fileutils.h"
+
+namespace talk_base {
+
+class Win32Filesystem : public FilesystemInterface {
+ public:
+ // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise,
+ // returns NULL.
+ virtual FileStream *OpenFile(const Pathname &filename,
+ const std::string &mode);
+
+ // Atomically creates an empty file accessible only to the current user if one
+ // does not already exist at the given path, otherwise fails.
+ virtual bool CreatePrivateFile(const Pathname &filename);
+
+ // This will attempt to delete the path located at filename.
+ // If the path points to a folder, it will fail with VERIFY
+ virtual bool DeleteFile(const Pathname &filename);
+
+ // This will attempt to delete an empty folder. If the path does not point to
+ // a folder, it fails with VERIFY. If the folder is not empty, it fails normally
+ virtual bool DeleteEmptyFolder(const Pathname &folder);
+
+ // Creates a directory. This will call itself recursively to create /foo/bar even if
+ // /foo does not exist.
+ // Returns TRUE if function succeeds
+ virtual bool CreateFolder(const Pathname &pathname);
+
+ // This moves a file from old_path to new_path. If the new path is on a
+ // different volume than the old, it will attempt to copy and then delete
+ // the folder
+ // Returns true if the file is successfully moved
+ virtual bool MoveFile(const Pathname &old_path, const Pathname &new_path);
+
+ // Moves a folder from old_path to new_path. If the new path is on a different
+ // volume from the old, it will attempt to Copy and then Delete the folder
+ // Returns true if the folder is successfully moved
+ virtual bool MoveFolder(const Pathname &old_path, const Pathname &new_path);
+
+ // This copies a file from old_path to _new_path
+ // Returns true if function succeeds
+ virtual bool CopyFile(const Pathname &old_path, const Pathname &new_path);
+
+ // Returns true if a pathname is a directory
+ virtual bool IsFolder(const Pathname& pathname);
+
+ // Returns true if a file exists at path
+ virtual bool IsFile(const Pathname &path);
+
+ // Returns true if pathname refers to no filesystem object, every parent
+ // directory either exists, or is also absent.
+ virtual bool IsAbsent(const Pathname& pathname);
+
+ // Returns true if pathname represents a temporary location on the system.
+ virtual bool IsTemporaryPath(const Pathname& pathname);
+
+ // All of the following functions set pathname and return true if successful.
+ // Returned paths always include a trailing backslash.
+ // If create is true, the path will be recursively created.
+ // If append is non-NULL, it will be appended (and possibly created).
+
+ virtual std::string TempFilename(const Pathname &dir, const std::string &prefix);
+
+ virtual bool GetFileSize(const Pathname& path, size_t* size);
+ virtual bool GetFileTime(const Pathname& path, FileTimeType which,
+ time_t* time);
+
+ // A folder appropriate for storing temporary files (Contents are
+ // automatically deleted when the program exists)
+ virtual bool GetTemporaryFolder(Pathname &path, bool create,
+ const std::string *append);
+
+ // Returns the path to the running application.
+ virtual bool GetAppPathname(Pathname* path);
+
+ virtual bool GetAppDataFolder(Pathname* path, bool per_user);
+
+ // Get a temporary folder that is unique to the current user and application.
+ virtual bool GetAppTempFolder(Pathname* path);
+
+ virtual bool GetDiskFreeSpace(const Pathname& path, int64 *freebytes);
+
+ virtual Pathname GetCurrentDirectory();
+};
+
+} // namespace talk_base
+
+#endif // _WIN32FILESYSTEM_H__
diff --git a/talk/base/win32securityerrors.cc b/talk/base/win32securityerrors.cc
new file mode 100644
index 0000000..50f4f66
--- /dev/null
+++ b/talk/base/win32securityerrors.cc
@@ -0,0 +1,66 @@
+/*
+ * 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 "talk/base/win32.h"
+#include "talk/base/logging.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+
+extern const ConstantLabel SECURITY_ERRORS[];
+
+const ConstantLabel SECURITY_ERRORS[] = {
+ KLABEL(SEC_I_COMPLETE_AND_CONTINUE),
+ KLABEL(SEC_I_COMPLETE_NEEDED),
+ KLABEL(SEC_I_CONTEXT_EXPIRED),
+ KLABEL(SEC_I_CONTINUE_NEEDED),
+ KLABEL(SEC_I_INCOMPLETE_CREDENTIALS),
+ KLABEL(SEC_I_RENEGOTIATE),
+ KLABEL(SEC_E_CERT_EXPIRED),
+ KLABEL(SEC_E_INCOMPLETE_MESSAGE),
+ KLABEL(SEC_E_INSUFFICIENT_MEMORY),
+ KLABEL(SEC_E_INTERNAL_ERROR),
+ KLABEL(SEC_E_INVALID_HANDLE),
+ KLABEL(SEC_E_INVALID_TOKEN),
+ KLABEL(SEC_E_LOGON_DENIED),
+ KLABEL(SEC_E_NO_AUTHENTICATING_AUTHORITY),
+ KLABEL(SEC_E_NO_CREDENTIALS),
+ KLABEL(SEC_E_NOT_OWNER),
+ KLABEL(SEC_E_OK),
+ KLABEL(SEC_E_SECPKG_NOT_FOUND),
+ KLABEL(SEC_E_TARGET_UNKNOWN),
+ KLABEL(SEC_E_UNKNOWN_CREDENTIALS),
+ KLABEL(SEC_E_UNSUPPORTED_FUNCTION),
+ KLABEL(SEC_E_UNTRUSTED_ROOT),
+ KLABEL(SEC_E_WRONG_PRINCIPAL),
+ LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/win32socketinit.cc b/talk/base/win32socketinit.cc
new file mode 100644
index 0000000..f6ac666
--- /dev/null
+++ b/talk/base/win32socketinit.cc
@@ -0,0 +1,63 @@
+/*
+ * libjingle
+ * Copyright 2009, 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/win32socketinit.h"
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+// Please don't remove this function.
+void EnsureWinsockInit() {
+ // The default implementation uses a global initializer, so WSAStartup
+ // happens at module load time. Thus we don't need to do anything here.
+ // The hook is provided so that a client that statically links with
+ // libjingle can override it, to provide its own initialization.
+}
+
+#ifdef WIN32
+class WinsockInitializer {
+ public:
+ WinsockInitializer() {
+ WSADATA wsaData;
+ WORD wVersionRequested = MAKEWORD(1, 0);
+ err_ = WSAStartup(wVersionRequested, &wsaData);
+ }
+ ~WinsockInitializer() {
+ if (!err_)
+ WSACleanup();
+ }
+ int error() {
+ return err_;
+ }
+ private:
+ int err_;
+};
+WinsockInitializer g_winsockinit;
+#endif
+
+} // namespace talk_base
diff --git a/talk/base/win32socketinit.h b/talk/base/win32socketinit.h
new file mode 100644
index 0000000..f56b7ff
--- /dev/null
+++ b/talk/base/win32socketinit.h
@@ -0,0 +1,37 @@
+/*
+ * libjingle
+ * Copyright 2009, 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_WIN32SOCKETINIT_H_
+#define TALK_BASE_WIN32SOCKETINIT_H_
+
+namespace talk_base {
+
+void EnsureWinsockInit();
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WIN32SOCKETINIT_H_
diff --git a/talk/base/win32socketserver.cc b/talk/base/win32socketserver.cc
new file mode 100644
index 0000000..490799c
--- /dev/null
+++ b/talk/base/win32socketserver.cc
@@ -0,0 +1,811 @@
+/*
+ * 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 "talk/base/win32socketserver.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/winping.h"
+#include "talk/base/win32window.h"
+#include <ws2tcpip.h> // NOLINT
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+// TODO: Move this to a common place where PhysicalSocketServer can
+// share it.
+// Standard MTUs
+static const uint16 PACKET_MAXIMUMS[] = {
+ 65535, // Theoretical maximum, Hyperchannel
+ 32000, // Nothing
+ 17914, // 16Mb IBM Token Ring
+ 8166, // IEEE 802.4
+ // 4464 // IEEE 802.5 (4Mb max)
+ 4352, // FDDI
+ // 2048, // Wideband Network
+ 2002, // IEEE 802.5 (4Mb recommended)
+ // 1536, // Expermental Ethernet Networks
+ // 1500, // Ethernet, Point-to-Point (default)
+ 1492, // IEEE 802.3
+ 1006, // SLIP, ARPANET
+ // 576, // X.25 Networks
+ // 544, // DEC IP Portal
+ // 512, // NETBIOS
+ 508, // IEEE 802/Source-Rt Bridge, ARCNET
+ 296, // Point-to-Point (low delay)
+ 68, // Official minimum
+ 0, // End of list marker
+};
+
+static const uint32 IP_HEADER_SIZE = 20;
+static const uint32 ICMP_HEADER_SIZE = 8;
+
+// TODO: Enable for production builds also? Use FormatMessage?
+#ifdef _DEBUG
+LPCSTR WSAErrorToString(int error, LPCSTR *description_result) {
+ LPCSTR string = "Unspecified";
+ LPCSTR description = "Unspecified description";
+ switch (error) {
+ case ERROR_SUCCESS:
+ string = "SUCCESS";
+ description = "Operation succeeded";
+ break;
+ case WSAEWOULDBLOCK:
+ string = "WSAEWOULDBLOCK";
+ description = "Using a non-blocking socket, will notify later";
+ break;
+ case WSAEACCES:
+ string = "WSAEACCES";
+ description = "Access denied, or sharing violation";
+ break;
+ case WSAEADDRNOTAVAIL:
+ string = "WSAEADDRNOTAVAIL";
+ description = "Address is not valid in this context";
+ break;
+ case WSAENETDOWN:
+ string = "WSAENETDOWN";
+ description = "Network is down";
+ break;
+ case WSAENETUNREACH:
+ string = "WSAENETUNREACH";
+ description = "Network is up, but unreachable";
+ break;
+ case WSAENETRESET:
+ string = "WSANETRESET";
+ description = "Connection has been reset due to keep-alive activity";
+ break;
+ case WSAECONNABORTED:
+ string = "WSAECONNABORTED";
+ description = "Aborted by host";
+ break;
+ case WSAECONNRESET:
+ string = "WSAECONNRESET";
+ description = "Connection reset by host";
+ break;
+ case WSAETIMEDOUT:
+ string = "WSAETIMEDOUT";
+ description = "Timed out, host failed to respond";
+ break;
+ case WSAECONNREFUSED:
+ string = "WSAECONNREFUSED";
+ description = "Host actively refused connection";
+ break;
+ case WSAEHOSTDOWN:
+ string = "WSAEHOSTDOWN";
+ description = "Host is down";
+ break;
+ case WSAEHOSTUNREACH:
+ string = "WSAEHOSTUNREACH";
+ description = "Host is unreachable";
+ break;
+ case WSAHOST_NOT_FOUND:
+ string = "WSAHOST_NOT_FOUND";
+ description = "No such host is known";
+ break;
+ }
+ if (description_result) {
+ *description_result = description;
+ }
+ return string;
+}
+
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {
+ LPCSTR description_string;
+ LPCSTR error_string = WSAErrorToString(error, &description_string);
+ LOG(LS_INFO) << context << " = " << error
+ << " (" << error_string << ":" << description_string << ") ["
+ << address.ToString() << "]";
+}
+#else
+void ReportWSAError(LPCSTR context, int error, const SocketAddress& address) {}
+#endif
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket::EventSink
+/////////////////////////////////////////////////////////////////////////////
+
+#define WM_SOCKETNOTIFY (WM_USER + 50)
+#define WM_DNSNOTIFY (WM_USER + 51)
+
+struct Win32Socket::DnsLookup {
+ HANDLE handle;
+ uint16 port;
+ char buffer[MAXGETHOSTSTRUCT];
+};
+
+class Win32Socket::EventSink : public Win32Window {
+ public:
+ explicit EventSink(Win32Socket * parent) : parent_(parent) { }
+
+ void Dispose();
+
+ virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result);
+ virtual void OnFinalMessage(HWND hWnd);
+
+ private:
+ bool OnSocketNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& result);
+ bool OnDnsNotify(WPARAM wParam, LPARAM lParam, LRESULT& result);
+
+ Win32Socket * parent_;
+};
+
+void Win32Socket::EventSink::Dispose() {
+ parent_ = NULL;
+ if (::IsWindow(handle())) {
+ ::DestroyWindow(handle());
+ } else {
+ delete this;
+ }
+}
+
+bool Win32Socket::EventSink::OnMessage(UINT uMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT& result) {
+ switch (uMsg) {
+ case WM_SOCKETNOTIFY:
+ case WM_TIMER:
+ return OnSocketNotify(uMsg, wParam, lParam, result);
+ case WM_DNSNOTIFY:
+ return OnDnsNotify(wParam, lParam, result);
+ }
+ return false;
+}
+
+bool Win32Socket::EventSink::OnSocketNotify(UINT uMsg, WPARAM wParam,
+ LPARAM lParam, LRESULT& result) {
+ result = 0;
+
+ int wsa_event = WSAGETSELECTEVENT(lParam);
+ int wsa_error = WSAGETSELECTERROR(lParam);
+
+ // Treat connect timeouts as close notifications
+ if (uMsg == WM_TIMER) {
+ wsa_event = FD_CLOSE;
+ wsa_error = WSAETIMEDOUT;
+ }
+
+ if (parent_)
+ parent_->OnSocketNotify(static_cast<SOCKET>(wParam), wsa_event, wsa_error);
+ return true;
+}
+
+bool Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam,
+ LRESULT& result) {
+ result = 0;
+
+ int error = WSAGETASYNCERROR(lParam);
+ if (parent_)
+ parent_->OnDnsNotify(reinterpret_cast<HANDLE>(wParam), error);
+ return true;
+}
+
+void Win32Socket::EventSink::OnFinalMessage(HWND hWnd) {
+ delete this;
+}
+
+/////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+/////////////////////////////////////////////////////////////////////////////
+
+Win32Socket::Win32Socket()
+ : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), connect_time_(0),
+ closing_(false), close_error_(0), sink_(NULL), dns_(NULL) {
+}
+
+Win32Socket::~Win32Socket() {
+ Close();
+}
+
+bool Win32Socket::CreateT(int type) {
+ Close();
+ int proto = (SOCK_DGRAM == type) ? IPPROTO_UDP : IPPROTO_TCP;
+ socket_ = ::WSASocket(AF_INET, type, proto, NULL, NULL, 0);
+ if (socket_ == INVALID_SOCKET) {
+ UpdateLastError();
+ return false;
+ }
+ if ((SOCK_DGRAM == type) && !SetAsync(FD_READ | FD_WRITE)) {
+ return false;
+ }
+ return true;
+}
+
+int Win32Socket::Attach(SOCKET s) {
+ ASSERT(socket_ == INVALID_SOCKET);
+ if (socket_ != INVALID_SOCKET)
+ return SOCKET_ERROR;
+
+ ASSERT(s != INVALID_SOCKET);
+ if (s == INVALID_SOCKET)
+ return SOCKET_ERROR;
+
+ socket_ = s;
+ state_ = CS_CONNECTED;
+
+ if (!SetAsync(FD_READ | FD_WRITE | FD_CLOSE))
+ return SOCKET_ERROR;
+
+ return 0;
+}
+
+void Win32Socket::SetTimeout(int ms) {
+ if (sink_)
+ ::SetTimer(sink_->handle(), 1, ms, 0);
+}
+
+SocketAddress Win32Socket::GetLocalAddress() const {
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+ int result = ::getsockname(socket_, reinterpret_cast<sockaddr*>(&addr),
+ &addrlen);
+ SocketAddress address;
+ if (result >= 0) {
+ ASSERT(addrlen == sizeof(addr));
+ address.FromSockAddr(addr);
+ } else {
+ LOG(LS_WARNING) << "GetLocalAddress: unable to get local addr, socket="
+ << socket_;
+ }
+ return address;
+}
+
+SocketAddress Win32Socket::GetRemoteAddress() const {
+ sockaddr_in addr;
+ socklen_t addrlen = sizeof(addr);
+ int result = ::getpeername(socket_, reinterpret_cast<sockaddr*>(&addr),
+ &addrlen);
+ ASSERT(addrlen == sizeof(addr));
+ SocketAddress address;
+ if (result >= 0) {
+ ASSERT(addrlen == sizeof(addr));
+ address.FromSockAddr(addr);
+ } else {
+ LOG(LS_WARNING) << "GetRemoteAddress: unable to get remote addr, socket="
+ << socket_;
+ }
+ return address;
+}
+
+int Win32Socket::Bind(const SocketAddress& addr) {
+ ASSERT(socket_ != INVALID_SOCKET);
+ if (socket_ == INVALID_SOCKET)
+ return SOCKET_ERROR;
+
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ int err = ::bind(socket_, reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr));
+ UpdateLastError();
+ return err;
+}
+
+int Win32Socket::Connect(const SocketAddress& addr) {
+ if ((socket_ == INVALID_SOCKET) && !CreateT(SOCK_STREAM))
+ return SOCKET_ERROR;
+
+ if (!sink_ && !SetAsync(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE))
+ return SOCKET_ERROR;
+
+ // If we have an IP address, connect now.
+ if (!addr.IsUnresolved()) {
+ return DoConnect(addr);
+ }
+
+ LOG_F(LS_INFO) << "async dns lookup (" << addr.IPAsString() << ")";
+ DnsLookup * dns = new DnsLookup;
+ dns->handle = WSAAsyncGetHostByName(sink_->handle(), WM_DNSNOTIFY,
+ addr.IPAsString().c_str(), dns->buffer, sizeof(dns->buffer));
+
+ if (!dns->handle) {
+ LOG_F(LS_ERROR) << "WSAAsyncGetHostByName error: " << WSAGetLastError();
+ delete dns;
+ UpdateLastError();
+ Close();
+ return SOCKET_ERROR;
+ }
+
+ dns->port = addr.port();
+ dns_ = dns;
+ state_ = CS_CONNECTING;
+ return 0;
+}
+
+int Win32Socket::DoConnect(const SocketAddress& addr) {
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ connect_time_ = Time();
+ int result = connect(socket_, reinterpret_cast<SOCKADDR*>(&saddr),
+ sizeof(saddr));
+ if (result != SOCKET_ERROR) {
+ state_ = CS_CONNECTED;
+ } else {
+ int code = WSAGetLastError();
+ if (code == WSAEWOULDBLOCK) {
+ state_ = CS_CONNECTING;
+ } else {
+ ReportWSAError("WSAAsync:connect", code, addr);
+ error_ = code;
+ Close();
+ return SOCKET_ERROR;
+ }
+ }
+ addr_ = addr;
+
+ return 0;
+}
+
+int Win32Socket::GetError() const {
+ return error_;
+}
+
+void Win32Socket::SetError(int error) {
+ error_ = error;
+}
+
+Socket::ConnState Win32Socket::GetState() const {
+ return state_;
+}
+
+int Win32Socket::GetOption(Option opt, int* value) {
+ int slevel;
+ int sopt;
+ if (TranslateOption(opt, &slevel, &sopt) == -1)
+ return -1;
+
+ char* p = reinterpret_cast<char*>(value);
+ int optlen = sizeof(value);
+ return ::getsockopt(socket_, slevel, sopt, p, &optlen);
+}
+
+int Win32Socket::SetOption(Option opt, int value) {
+ int slevel;
+ int sopt;
+ if (TranslateOption(opt, &slevel, &sopt) == -1)
+ return -1;
+
+ const char* p = reinterpret_cast<const char*>(&value);
+ return ::setsockopt(socket_, slevel, sopt, p, sizeof(value));
+}
+
+int Win32Socket::Send(const void *pv, size_t cb) {
+ int sent = ::send(socket_, reinterpret_cast<const char*>(pv), cb, 0);
+ UpdateLastError();
+ return sent;
+}
+
+int Win32Socket::SendTo(const void *pv, size_t cb,
+ const SocketAddress& addr) {
+ sockaddr_in saddr;
+ addr.ToSockAddr(&saddr);
+ int sent = ::sendto(socket_, reinterpret_cast<const char*>(pv), cb, 0,
+ reinterpret_cast<sockaddr*>(&saddr), sizeof(saddr));
+ UpdateLastError();
+ return sent;
+}
+
+int Win32Socket::Recv(void *pv, size_t cb) {
+ int received = ::recv(socket_, static_cast<char*>(pv), cb, 0);
+ UpdateLastError();
+ if (closing_ && received <= static_cast<int>(cb))
+ PostClosed();
+ return received;
+}
+
+int Win32Socket::RecvFrom(void *pv, size_t cb,
+ SocketAddress *paddr) {
+ sockaddr_in saddr;
+ socklen_t cbAddr = sizeof(saddr);
+ int received = ::recvfrom(socket_, static_cast<char*>(pv), cb, 0,
+ reinterpret_cast<sockaddr*>(&saddr), &cbAddr);
+ UpdateLastError();
+ if (received != SOCKET_ERROR)
+ paddr->FromSockAddr(saddr);
+ if (closing_ && received <= static_cast<int>(cb))
+ PostClosed();
+ return received;
+}
+
+int Win32Socket::Listen(int backlog) {
+ int err = ::listen(socket_, backlog);
+ if (!SetAsync(FD_ACCEPT))
+ return SOCKET_ERROR;
+
+ UpdateLastError();
+ if (err == 0)
+ state_ = CS_CONNECTING;
+ return err;
+}
+
+Win32Socket* Win32Socket::Accept(SocketAddress *paddr) {
+ sockaddr_in saddr;
+ socklen_t cbAddr = sizeof(saddr);
+ SOCKET s = ::accept(socket_, reinterpret_cast<sockaddr*>(&saddr), &cbAddr);
+ UpdateLastError();
+ if (s == INVALID_SOCKET)
+ return NULL;
+ if (paddr)
+ paddr->FromSockAddr(saddr);
+ Win32Socket* socket = new Win32Socket;
+ if (0 == socket->Attach(s))
+ return socket;
+ delete socket;
+ return NULL;
+}
+
+int Win32Socket::Close() {
+ int err = 0;
+ if (socket_ != INVALID_SOCKET) {
+ err = ::closesocket(socket_);
+ socket_ = INVALID_SOCKET;
+ closing_ = false;
+ close_error_ = 0;
+ UpdateLastError();
+ }
+ if (dns_) {
+ WSACancelAsyncRequest(dns_->handle);
+ delete dns_;
+ dns_ = NULL;
+ }
+ if (sink_) {
+ sink_->Dispose();
+ sink_ = NULL;
+ }
+ addr_.Clear();
+ state_ = CS_CLOSED;
+ return err;
+}
+
+int Win32Socket::EstimateMTU(uint16* mtu) {
+ SocketAddress addr = GetRemoteAddress();
+ if (addr.IsAny()) {
+ error_ = ENOTCONN;
+ return -1;
+ }
+
+ WinPing ping;
+ if (!ping.IsValid()) {
+ error_ = EINVAL; // can't think of a better error ID
+ return -1;
+ }
+
+ for (int level = 0; PACKET_MAXIMUMS[level + 1] > 0; ++level) {
+ int32 size = PACKET_MAXIMUMS[level] - IP_HEADER_SIZE - ICMP_HEADER_SIZE;
+ WinPing::PingResult result = ping.Ping(addr.ip(), size, 0, 1, false);
+ if (result == WinPing::PING_FAIL) {
+ error_ = EINVAL; // can't think of a better error ID
+ return -1;
+ }
+ if (result != WinPing::PING_TOO_LARGE) {
+ *mtu = PACKET_MAXIMUMS[level];
+ return 0;
+ }
+ }
+
+ ASSERT(false);
+ return 0;
+}
+
+bool Win32Socket::SetAsync(int events) {
+ ASSERT(NULL == sink_);
+
+ // Create window
+ sink_ = new EventSink(this);
+ sink_->Create(NULL, L"EventSink", 0, 0, 0, 0, 10, 10);
+
+ // start the async select
+ if (WSAAsyncSelect(socket_, sink_->handle(), WM_SOCKETNOTIFY, events)
+ == SOCKET_ERROR) {
+ UpdateLastError();
+ Close();
+ return false;
+ }
+
+ return true;
+}
+
+bool Win32Socket::HandleClosed(int close_error) {
+ // WM_CLOSE will be received before all data has been read, so we need to
+ // hold on to it until the read buffer has been drained.
+ char ch;
+ closing_ = true;
+ close_error_ = close_error;
+ return (::recv(socket_, &ch, 1, MSG_PEEK) <= 0);
+}
+
+void Win32Socket::PostClosed() {
+ // If we see that the buffer is indeed drained, then send the close.
+ closing_ = false;
+ ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY,
+ socket_, WSAMAKESELECTREPLY(FD_CLOSE, close_error_));
+}
+
+void Win32Socket::UpdateLastError() {
+ error_ = WSAGetLastError();
+}
+
+int Win32Socket::TranslateOption(Option opt, int* slevel, int* sopt) {
+ switch (opt) {
+ case OPT_DONTFRAGMENT:
+ *slevel = IPPROTO_IP;
+ *sopt = IP_DONTFRAGMENT;
+ break;
+ case OPT_RCVBUF:
+ *slevel = SOL_SOCKET;
+ *sopt = SO_RCVBUF;
+ break;
+ case OPT_SNDBUF:
+ *slevel = SOL_SOCKET;
+ *sopt = SO_SNDBUF;
+ break;
+ case OPT_NODELAY:
+ *slevel = IPPROTO_TCP;
+ *sopt = TCP_NODELAY;
+ break;
+ default:
+ ASSERT(false);
+ return -1;
+ }
+ return 0;
+}
+
+void Win32Socket::OnSocketNotify(SOCKET socket, int event, int error) {
+ // Ignore events if we're already closed.
+ if (socket != socket_)
+ return;
+
+ error_ = error;
+ switch (event) {
+ case FD_CONNECT:
+ if (error != ERROR_SUCCESS) {
+ ReportWSAError("WSAAsync:connect notify", error, addr_);
+#ifdef _DEBUG
+ int32 duration = TimeSince(connect_time_);
+ LOG(LS_INFO) << "WSAAsync:connect error (" << duration
+ << " ms), faking close";
+#endif
+ state_ = CS_CLOSED;
+ // If you get an error connecting, close doesn't really do anything
+ // and it certainly doesn't send back any close notification, but
+ // we really only maintain a few states, so it is easiest to get
+ // back into a known state by pretending that a close happened, even
+ // though the connect event never did occur.
+ SignalCloseEvent(this, error);
+ } else {
+#ifdef _DEBUG
+ int32 duration = TimeSince(connect_time_);
+ LOG(LS_INFO) << "WSAAsync:connect (" << duration << " ms)";
+#endif
+ state_ = CS_CONNECTED;
+ SignalConnectEvent(this);
+ }
+ break;
+
+ case FD_ACCEPT:
+ case FD_READ:
+ if (error != ERROR_SUCCESS) {
+ ReportWSAError("WSAAsync:read notify", error, addr_);
+ } else {
+ SignalReadEvent(this);
+ }
+ break;
+
+ case FD_WRITE:
+ if (error != ERROR_SUCCESS) {
+ ReportWSAError("WSAAsync:write notify", error, addr_);
+ } else {
+ SignalWriteEvent(this);
+ }
+ break;
+
+ case FD_CLOSE:
+ if (HandleClosed(error)) {
+ ReportWSAError("WSAAsync:close notify", error, addr_);
+ state_ = CS_CLOSED;
+ SignalCloseEvent(this, error);
+ }
+ break;
+ }
+}
+
+void Win32Socket::OnDnsNotify(HANDLE task, int error) {
+ if (!dns_ || dns_->handle != task)
+ return;
+
+ uint32 ip = 0;
+ if (error == 0) {
+ hostent* pHost = reinterpret_cast<hostent*>(dns_->buffer);
+ uint32 net_ip = *reinterpret_cast<uint32*>(pHost->h_addr_list[0]);
+ ip = NetworkToHost32(net_ip);
+ }
+
+ LOG_F(LS_INFO) << "(" << SocketAddress::IPToString(ip)
+ << ", " << error << ")";
+
+ if (error == 0) {
+ SocketAddress address(ip, dns_->port);
+ error = DoConnect(address);
+ } else {
+ Close();
+ }
+
+ if (error) {
+ error_ = error;
+ SignalCloseEvent(this, error_);
+ } else {
+ delete dns_;
+ dns_ = NULL;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+// Provides cricket base services on top of a win32 gui thread
+///////////////////////////////////////////////////////////////////////////////
+
+static UINT s_wm_wakeup_id = 0;
+const TCHAR Win32SocketServer::kWindowName[] = L"libjingle Message Window";
+
+Win32SocketServer::Win32SocketServer(MessageQueue *message_queue)
+ : message_queue_(message_queue), wnd_(this), posted_(false) {
+ if (s_wm_wakeup_id == 0)
+ s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP");
+ if (!wnd_.Create(NULL, kWindowName, 0, 0, 0, 0, 0, 0)) {
+ LOG_GLE(LS_ERROR) << "Failed to create message window.";
+ }
+}
+
+Win32SocketServer::~Win32SocketServer() {
+ if (wnd_.handle() != NULL) {
+ KillTimer(wnd_.handle(), 1);
+ wnd_.Destroy();
+ }
+}
+
+Socket* Win32SocketServer::CreateSocket(int type) {
+ return CreateAsyncSocket(type);
+}
+
+AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) {
+ Win32Socket* socket = new Win32Socket;
+ if (socket->CreateT(type)) {
+ return socket;
+ }
+ delete socket;
+ return NULL;
+}
+
+void Win32SocketServer::SetMessageQueue(MessageQueue* queue) {
+ message_queue_ = queue;
+}
+
+bool Win32SocketServer::Wait(int cms, bool process_io) {
+ BOOL b;
+ if (process_io) {
+ // Spin the Win32 message pump at least once, and as long as requested.
+ // This is the Thread::ProcessMessages case.
+ uint32 start = Time();
+ MSG msg;
+ do {
+ SetTimer(wnd_.handle(), 0, cms, NULL);
+ b = GetMessage(&msg, NULL, 0, 0);
+ if (b) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ KillTimer(wnd_.handle(), 0);
+ } while (b && TimeSince(start) < cms);
+ } else if (cms != 0) {
+ // Sit and wait forever for a WakeUp. This is the Thread::Send case.
+ ASSERT(cms == -1);
+ MSG msg;
+ b = GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id);
+ {
+ CritScope scope(&cs_);
+ posted_ = false;
+ }
+ } else {
+ // No-op (cms == 0 && !process_io). This is the Pump case.
+ b = TRUE;
+ }
+ return (b != FALSE);
+}
+
+void Win32SocketServer::WakeUp() {
+ if (wnd_.handle()) {
+ // Set the "message pending" flag, if not already set.
+ {
+ CritScope scope(&cs_);
+ if (posted_)
+ return;
+ posted_ = true;
+ }
+
+ PostMessage(wnd_.handle(), s_wm_wakeup_id, 0, 0);
+ }
+}
+
+void Win32SocketServer::Pump() {
+ // Clear the "message pending" flag.
+ {
+ CritScope scope(&cs_);
+ posted_ = false;
+ }
+
+ // Dispatch all the messages that are currently in our queue. If new messages
+ // are posted during the dispatch, they will be handled in the next Pump.
+ // We use max(1, ...) to make sure we try to dispatch at least once, since
+ // this allow us to process "sent" messages, not included in the size() count.
+ Message msg;
+ for (size_t max_messages_to_process = _max<size_t>(1, message_queue_->size());
+ max_messages_to_process > 0 && message_queue_->Get(&msg, 0, false);
+ --max_messages_to_process) {
+ message_queue_->Dispatch(&msg);
+ }
+
+ // Anything remaining?
+ int delay = message_queue_->GetDelay();
+ if (delay == -1) {
+ KillTimer(wnd_.handle(), 1);
+ } else {
+ SetTimer(wnd_.handle(), 1, delay, NULL);
+ }
+}
+
+bool Win32SocketServer::MessageWindow::OnMessage(UINT wm, WPARAM wp,
+ LPARAM lp, LRESULT& lr) {
+ bool handled = false;
+ if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) {
+ ss_->Pump();
+ lr = 0;
+ handled = true;
+ }
+ return handled;
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32socketserver.h b/talk/base/win32socketserver.h
new file mode 100644
index 0000000..f14a197
--- /dev/null
+++ b/talk/base/win32socketserver.h
@@ -0,0 +1,174 @@
+/*
+ * 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_WIN32SOCKETSERVER_H_
+#define TALK_BASE_WIN32SOCKETSERVER_H_
+
+#ifdef WIN32
+#include "talk/base/asyncsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/socketserver.h"
+#include "talk/base/socketfactory.h"
+#include "talk/base/socket.h"
+#include "talk/base/thread.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Socket
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Socket : public AsyncSocket {
+ public:
+ Win32Socket();
+ virtual ~Win32Socket();
+
+ bool CreateT(int type);
+
+ int Attach(SOCKET s);
+ void SetTimeout(int ms);
+
+ // AsyncSocket Interface
+ virtual SocketAddress GetLocalAddress() const;
+ virtual SocketAddress GetRemoteAddress() const;
+ virtual int Bind(const SocketAddress& addr);
+ virtual int Connect(const SocketAddress& addr);
+ virtual int Send(const void *pv, size_t cb);
+ virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr);
+ virtual int Recv(void *pv, size_t cb);
+ virtual int RecvFrom(void *pv, size_t cb, SocketAddress *paddr);
+ virtual int Listen(int backlog);
+ virtual Win32Socket *Accept(SocketAddress *paddr);
+ virtual int Close();
+ virtual int GetError() const;
+ virtual void SetError(int error);
+ virtual ConnState GetState() const;
+ virtual int EstimateMTU(uint16* mtu);
+ virtual int GetOption(Option opt, int* value);
+ virtual int SetOption(Option opt, int value);
+
+ private:
+ bool SetAsync(int events);
+ int DoConnect(const SocketAddress& addr);
+ bool HandleClosed(int close_error);
+ void PostClosed();
+ void UpdateLastError();
+ static int TranslateOption(Option opt, int* slevel, int* sopt);
+
+ void OnSocketNotify(SOCKET socket, int event, int error);
+ void OnDnsNotify(HANDLE task, int error);
+
+ SOCKET socket_;
+ int error_;
+ ConnState state_;
+ SocketAddress addr_; // address that we connected to (see DoConnect)
+ uint32 connect_time_;
+ bool closing_;
+ int close_error_;
+
+ class EventSink;
+ friend class EventSink;
+ EventSink * sink_;
+
+ struct DnsLookup;
+ DnsLookup * dns_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32SocketServer
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32SocketServer : public SocketServer {
+ public:
+ explicit Win32SocketServer(MessageQueue *message_queue);
+ virtual ~Win32SocketServer();
+
+ // SocketServer Interface
+ virtual Socket* CreateSocket(int type);
+ virtual AsyncSocket* CreateAsyncSocket(int type);
+ virtual void SetMessageQueue(MessageQueue* queue);
+ virtual bool Wait(int cms, bool process_io);
+ virtual void WakeUp();
+
+ void Pump();
+
+ HWND handle() { return wnd_.handle(); }
+
+ private:
+ class MessageWindow : public Win32Window {
+ public:
+ explicit MessageWindow(Win32SocketServer* ss) : ss_(ss) {}
+ private:
+ virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+ Win32SocketServer* ss_;
+ };
+
+ static const TCHAR kWindowName[];
+ MessageQueue *message_queue_;
+ MessageWindow wnd_;
+ CriticalSection cs_;
+ bool posted_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Thread. Automatically pumps Windows messages.
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Thread : public Thread {
+ public:
+ Win32Thread() : ss_(this), id_(0) {
+ set_socketserver(&ss_);
+ }
+ virtual ~Win32Thread() {
+ set_socketserver(NULL);
+ }
+ virtual void Run() {
+ MSG msg;
+ id_ = GetCurrentThreadId();
+ while (GetMessage(&msg, NULL, 0, 0)) {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ id_ = 0;
+ }
+ virtual void Quit() {
+ PostThreadMessage(id_, WM_QUIT, 0, 0);
+ }
+ private:
+ Win32SocketServer ss_;
+ DWORD id_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // WIN32
+
+#endif // TALK_BASE_WIN32SOCKETSERVER_H_
diff --git a/talk/base/win32window.cc b/talk/base/win32window.cc
new file mode 100644
index 0000000..0e7761f
--- /dev/null
+++ b/talk/base/win32window.cc
@@ -0,0 +1,134 @@
+/*
+ * 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 "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/win32window.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+static const wchar_t kWindowBaseClassName[] = L"WindowBaseClass";
+HINSTANCE Win32Window::instance_ = GetModuleHandle(NULL);
+ATOM Win32Window::window_class_ = 0;
+
+Win32Window::Win32Window() : wnd_(NULL) {
+}
+
+Win32Window::~Win32Window() {
+ ASSERT(NULL == wnd_);
+}
+
+bool Win32Window::Create(HWND parent, const wchar_t* title, DWORD style,
+ DWORD exstyle, int x, int y, int cx, int cy) {
+ if (wnd_) {
+ // Window already exists.
+ return false;
+ }
+
+ if (!window_class_) {
+ // Class not registered, register it.
+ WNDCLASSEX wcex;
+ memset(&wcex, 0, sizeof(wcex));
+ wcex.cbSize = sizeof(wcex);
+ wcex.hInstance = instance_;
+ wcex.lpfnWndProc = &Win32Window::WndProc;
+ wcex.lpszClassName = kWindowBaseClassName;
+ window_class_ = ::RegisterClassEx(&wcex);
+ if (!window_class_) {
+ LOG_GLE(LS_ERROR) << "RegisterClassEx failed";
+ return false;
+ }
+ }
+ wnd_ = ::CreateWindowEx(exstyle, kWindowBaseClassName, title, style,
+ x, y, cx, cy, parent, NULL, instance_, this);
+ return (NULL != wnd_);
+}
+
+void Win32Window::Destroy() {
+ VERIFY(::DestroyWindow(wnd_) != FALSE);
+}
+
+void Win32Window::SetInstance(HINSTANCE instance) {
+ instance_ = instance;
+}
+
+void Win32Window::Shutdown() {
+ if (window_class_) {
+ ::UnregisterClass(MAKEINTATOM(window_class_), instance_);
+ window_class_ = 0;
+ }
+}
+
+bool Win32Window::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result) {
+ switch (uMsg) {
+ case WM_CLOSE:
+ if (!OnClose()) {
+ result = 0;
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+LRESULT Win32Window::WndProc(HWND hwnd, UINT uMsg,
+ WPARAM wParam, LPARAM lParam) {
+ Win32Window* that = reinterpret_cast<Win32Window*>(
+ ::GetWindowLongPtr(hwnd, GWL_USERDATA));
+ if (!that && (WM_CREATE == uMsg)) {
+ CREATESTRUCT* cs = reinterpret_cast<CREATESTRUCT*>(lParam);
+ that = static_cast<Win32Window*>(cs->lpCreateParams);
+ that->wnd_ = hwnd;
+ ::SetWindowLongPtr(hwnd, GWL_USERDATA, reinterpret_cast<LONG_PTR>(that));
+ }
+ if (that) {
+ LRESULT result;
+ bool handled = that->OnMessage(uMsg, wParam, lParam, result);
+ if (WM_DESTROY == uMsg) {
+ for (HWND child = ::GetWindow(hwnd, GW_CHILD); child;
+ child = ::GetWindow(child, GW_HWNDNEXT)) {
+ LOG(LS_INFO) << "Child window: " << static_cast<void*>(child);
+ }
+ }
+ if (WM_NCDESTROY == uMsg) {
+ ::SetWindowLongPtr(hwnd, GWL_USERDATA, NULL);
+ that->wnd_ = NULL;
+ that->OnDestroyed();
+ }
+ if (handled) {
+ return result;
+ }
+ }
+ return ::DefWindowProc(hwnd, uMsg, wParam, lParam);
+}
+
+} // namespace talk_base
diff --git a/talk/base/win32window.h b/talk/base/win32window.h
new file mode 100644
index 0000000..66a56ce
--- /dev/null
+++ b/talk/base/win32window.h
@@ -0,0 +1,79 @@
+/*
+ * 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_WIN32WINDOW_H_
+#define TALK_BASE_WIN32WINDOW_H_
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+
+namespace talk_base {
+
+///////////////////////////////////////////////////////////////////////////////
+// Win32Window
+///////////////////////////////////////////////////////////////////////////////
+
+class Win32Window {
+ public:
+ Win32Window();
+ virtual ~Win32Window();
+
+ HWND handle() const { return wnd_; }
+
+ bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle,
+ int x, int y, int cx, int cy);
+ void Destroy();
+
+ // Call this first if you are running inside a DLL.
+ static void SetInstance(HINSTANCE instance);
+ // Call this when your DLL unloads.
+ static void Shutdown();
+
+ protected:
+ virtual bool OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result);
+
+ virtual bool OnClose() { return true; }
+ virtual void OnDestroyed() { }
+
+ private:
+ static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam,
+ LPARAM lParam);
+
+ HWND wnd_;
+ static HINSTANCE instance_;
+ static ATOM window_class_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // WIN32
+
+#endif // TALK_BASE_WIN32WINDOW_H_
diff --git a/talk/base/winfirewall.cc b/talk/base/winfirewall.cc
new file mode 100644
index 0000000..e87ee5a
--- /dev/null
+++ b/talk/base/winfirewall.cc
@@ -0,0 +1,172 @@
+/*
+ * 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 "talk/base/winfirewall.h"
+
+#include "talk/base/win32.h"
+
+#include <comdef.h>
+#include <netfw.h>
+
+#define RELEASE(lpUnk) do { \
+ if ((lpUnk) != NULL) { \
+ (lpUnk)->Release(); \
+ (lpUnk) = NULL; \
+ } \
+} while (0)
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+WinFirewall::WinFirewall() : mgr_(NULL), policy_(NULL), profile_(NULL) {
+}
+
+WinFirewall::~WinFirewall() {
+ Shutdown();
+}
+
+bool WinFirewall::Initialize(HRESULT* result) {
+ if (mgr_) {
+ if (result) {
+ *result = S_OK;
+ }
+ return true;
+ }
+
+ HRESULT hr = CoCreateInstance(__uuidof(NetFwMgr),
+ 0, CLSCTX_INPROC_SERVER,
+ __uuidof(INetFwMgr),
+ reinterpret_cast<void **>(&mgr_));
+ if (SUCCEEDED(hr) && (mgr_ != NULL))
+ hr = mgr_->get_LocalPolicy(&policy_);
+ if (SUCCEEDED(hr) && (policy_ != NULL))
+ hr = policy_->get_CurrentProfile(&profile_);
+
+ if (result)
+ *result = hr;
+ return SUCCEEDED(hr) && (profile_ != NULL);
+}
+
+void WinFirewall::Shutdown() {
+ RELEASE(profile_);
+ RELEASE(policy_);
+ RELEASE(mgr_);
+}
+
+bool WinFirewall::Enabled() const {
+ if (!profile_)
+ return false;
+
+ VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+ profile_->get_FirewallEnabled(&fwEnabled);
+ return (fwEnabled != VARIANT_FALSE);
+}
+
+bool WinFirewall::QueryAuthorized(const char* filename, bool* authorized)
+ const {
+ return QueryAuthorizedW(ToUtf16(filename).c_str(), authorized);
+}
+
+bool WinFirewall::QueryAuthorizedW(const wchar_t* filename, bool* authorized)
+ const {
+ *authorized = false;
+ bool success = false;
+
+ if (!profile_)
+ return false;
+
+ _bstr_t bfilename = filename;
+
+ INetFwAuthorizedApplications* apps = NULL;
+ HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+ if (SUCCEEDED(hr) && (apps != NULL)) {
+ INetFwAuthorizedApplication* app = NULL;
+ hr = apps->Item(bfilename, &app);
+ if (SUCCEEDED(hr) && (app != NULL)) {
+ VARIANT_BOOL fwEnabled = VARIANT_FALSE;
+ hr = app->get_Enabled(&fwEnabled);
+ app->Release();
+
+ if (SUCCEEDED(hr)) {
+ success = true;
+ *authorized = (fwEnabled != VARIANT_FALSE);
+ }
+ } else if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) {
+ // No entry in list of authorized apps
+ success = true;
+ } else {
+ // Unexpected error
+ }
+ apps->Release();
+ }
+
+ return success;
+}
+
+bool WinFirewall::AddApplication(const char* filename,
+ const char* friendly_name,
+ bool authorized,
+ HRESULT* result) {
+ return AddApplicationW(ToUtf16(filename).c_str(),
+ ToUtf16(friendly_name).c_str(), authorized, result);
+}
+
+bool WinFirewall::AddApplicationW(const wchar_t* filename,
+ const wchar_t* friendly_name,
+ bool authorized,
+ HRESULT* result) {
+ INetFwAuthorizedApplications* apps = NULL;
+ HRESULT hr = profile_->get_AuthorizedApplications(&apps);
+ if (SUCCEEDED(hr) && (apps != NULL)) {
+ INetFwAuthorizedApplication* app = NULL;
+ hr = CoCreateInstance(__uuidof(NetFwAuthorizedApplication),
+ 0, CLSCTX_INPROC_SERVER,
+ __uuidof(INetFwAuthorizedApplication),
+ reinterpret_cast<void **>(&app));
+ if (SUCCEEDED(hr) && (app != NULL)) {
+ _bstr_t bstr = filename;
+ hr = app->put_ProcessImageFileName(bstr);
+ bstr = friendly_name;
+ if (SUCCEEDED(hr))
+ hr = app->put_Name(bstr);
+ if (SUCCEEDED(hr))
+ hr = app->put_Enabled(authorized ? VARIANT_TRUE : VARIANT_FALSE);
+ if (SUCCEEDED(hr))
+ hr = apps->Add(app);
+ app->Release();
+ }
+ apps->Release();
+ }
+ if (result)
+ *result = hr;
+ return SUCCEEDED(hr);
+}
+
+} // namespace talk_base
diff --git a/talk/base/winfirewall.h b/talk/base/winfirewall.h
new file mode 100644
index 0000000..11d687e
--- /dev/null
+++ b/talk/base/winfirewall.h
@@ -0,0 +1,73 @@
+/*
+ * 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_WINFIREWALL_H_
+#define TALK_BASE_WINFIREWALL_H_
+
+#ifndef _HRESULT_DEFINED
+#define _HRESULT_DEFINED
+typedef long HRESULT; // Can't forward declare typedef, but don't need all win
+#endif // !_HRESULT_DEFINED
+
+struct INetFwMgr;
+struct INetFwPolicy;
+struct INetFwProfile;
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// WinFirewall
+//////////////////////////////////////////////////////////////////////
+
+class WinFirewall {
+ public:
+ WinFirewall();
+ ~WinFirewall();
+
+ bool Initialize(HRESULT* result);
+ void Shutdown();
+
+ bool Enabled() const;
+ bool QueryAuthorized(const char* filename, bool* authorized) const;
+ bool QueryAuthorizedW(const wchar_t* filename, bool* authorized) const;
+
+ bool AddApplication(const char* filename, const char* friendly_name,
+ bool authorized, HRESULT* result);
+ bool AddApplicationW(const wchar_t* filename, const wchar_t* friendly_name,
+ bool authorized, HRESULT* result);
+
+ private:
+ INetFwMgr* mgr_;
+ INetFwPolicy* policy_;
+ INetFwProfile* profile_;
+};
+
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
+
+#endif // TALK_BASE_WINFIREWALL_H_
diff --git a/talk/base/winping.cc b/talk/base/winping.cc
new file mode 100644
index 0000000..1802e03
--- /dev/null
+++ b/talk/base/winping.cc
@@ -0,0 +1,318 @@
+/*
+ * 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 "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/winping.h"
+#include "talk/base/logging.h"
+#include <cassert>
+
+namespace talk_base {
+
+//////////////////////////////////////////////////////////////////////
+// Found in IPExport.h
+//////////////////////////////////////////////////////////////////////
+
+typedef struct icmp_echo_reply {
+ ULONG Address; // Replying address
+ ULONG Status; // Reply IP_STATUS
+ ULONG RoundTripTime; // RTT in milliseconds
+ USHORT DataSize; // Reply data size in bytes
+ USHORT Reserved; // Reserved for system use
+ PVOID Data; // Pointer to the reply data
+ struct ip_option_information Options; // Reply options
+} ICMP_ECHO_REPLY, * PICMP_ECHO_REPLY;
+
+//
+// IP_STATUS codes returned from IP APIs
+//
+
+#define IP_STATUS_BASE 11000
+
+#define IP_SUCCESS 0
+#define IP_BUF_TOO_SMALL (IP_STATUS_BASE + 1)
+#define IP_DEST_NET_UNREACHABLE (IP_STATUS_BASE + 2)
+#define IP_DEST_HOST_UNREACHABLE (IP_STATUS_BASE + 3)
+#define IP_DEST_PROT_UNREACHABLE (IP_STATUS_BASE + 4)
+#define IP_DEST_PORT_UNREACHABLE (IP_STATUS_BASE + 5)
+#define IP_NO_RESOURCES (IP_STATUS_BASE + 6)
+#define IP_BAD_OPTION (IP_STATUS_BASE + 7)
+#define IP_HW_ERROR (IP_STATUS_BASE + 8)
+#define IP_PACKET_TOO_BIG (IP_STATUS_BASE + 9)
+#define IP_REQ_TIMED_OUT (IP_STATUS_BASE + 10)
+#define IP_BAD_REQ (IP_STATUS_BASE + 11)
+#define IP_BAD_ROUTE (IP_STATUS_BASE + 12)
+#define IP_TTL_EXPIRED_TRANSIT (IP_STATUS_BASE + 13)
+#define IP_TTL_EXPIRED_REASSEM (IP_STATUS_BASE + 14)
+#define IP_PARAM_PROBLEM (IP_STATUS_BASE + 15)
+#define IP_SOURCE_QUENCH (IP_STATUS_BASE + 16)
+#define IP_OPTION_TOO_BIG (IP_STATUS_BASE + 17)
+#define IP_BAD_DESTINATION (IP_STATUS_BASE + 18)
+
+#define IP_ADDR_DELETED (IP_STATUS_BASE + 19)
+#define IP_SPEC_MTU_CHANGE (IP_STATUS_BASE + 20)
+#define IP_MTU_CHANGE (IP_STATUS_BASE + 21)
+#define IP_UNLOAD (IP_STATUS_BASE + 22)
+#define IP_ADDR_ADDED (IP_STATUS_BASE + 23)
+#define IP_MEDIA_CONNECT (IP_STATUS_BASE + 24)
+#define IP_MEDIA_DISCONNECT (IP_STATUS_BASE + 25)
+#define IP_BIND_ADAPTER (IP_STATUS_BASE + 26)
+#define IP_UNBIND_ADAPTER (IP_STATUS_BASE + 27)
+#define IP_DEVICE_DOES_NOT_EXIST (IP_STATUS_BASE + 28)
+#define IP_DUPLICATE_ADDRESS (IP_STATUS_BASE + 29)
+#define IP_INTERFACE_METRIC_CHANGE (IP_STATUS_BASE + 30)
+#define IP_RECONFIG_SECFLTR (IP_STATUS_BASE + 31)
+#define IP_NEGOTIATING_IPSEC (IP_STATUS_BASE + 32)
+#define IP_INTERFACE_WOL_CAPABILITY_CHANGE (IP_STATUS_BASE + 33)
+#define IP_DUPLICATE_IPADD (IP_STATUS_BASE + 34)
+
+#define IP_GENERAL_FAILURE (IP_STATUS_BASE + 50)
+#define MAX_IP_STATUS IP_GENERAL_FAILURE
+#define IP_PENDING (IP_STATUS_BASE + 255)
+
+//
+// Values used in the IP header Flags field.
+//
+#define IP_FLAG_DF 0x2 // Don't fragment this packet.
+
+//
+// Supported IP Option Types.
+//
+// These types define the options which may be used in the OptionsData field
+// of the ip_option_information structure. See RFC 791 for a complete
+// description of each.
+//
+#define IP_OPT_EOL 0 // End of list option
+#define IP_OPT_NOP 1 // No operation
+#define IP_OPT_SECURITY 0x82 // Security option
+#define IP_OPT_LSRR 0x83 // Loose source route
+#define IP_OPT_SSRR 0x89 // Strict source route
+#define IP_OPT_RR 0x7 // Record route
+#define IP_OPT_TS 0x44 // Timestamp
+#define IP_OPT_SID 0x88 // Stream ID (obsolete)
+#define IP_OPT_ROUTER_ALERT 0x94 // Router Alert Option
+
+#define MAX_OPT_SIZE 40 // Maximum length of IP options in bytes
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Types
+//////////////////////////////////////////////////////////////////////
+
+const char * const ICMP_DLL_NAME = "icmp.dll";
+const char * const ICMP_CREATE_FUNC = "IcmpCreateFile";
+const char * const ICMP_CLOSE_FUNC = "IcmpCloseHandle";
+const char * const ICMP_SEND_FUNC = "IcmpSendEcho";
+
+inline uint32 ReplySize(uint32 data_size) {
+ // A ping error message is 8 bytes long, so make sure we allow for at least
+ // 8 bytes of reply data.
+ return sizeof(ICMP_ECHO_REPLY) + talk_base::_max<uint32>(8, data_size);
+}
+
+//////////////////////////////////////////////////////////////////////
+// WinPing
+//////////////////////////////////////////////////////////////////////
+
+WinPing::WinPing()
+ : dll_(0), hping_(INVALID_HANDLE_VALUE), create_(0), close_(0), send_(0),
+ data_(0), dlen_(0), reply_(0), rlen_(0), valid_(false) {
+
+ dll_ = LoadLibraryA(ICMP_DLL_NAME);
+ if (!dll_) {
+ LOG(LERROR) << "LoadLibrary: " << GetLastError();
+ return;
+ }
+
+ create_ = (PIcmpCreateFile) GetProcAddress(dll_, ICMP_CREATE_FUNC);
+ close_ = (PIcmpCloseHandle) GetProcAddress(dll_, ICMP_CLOSE_FUNC);
+ send_ = (PIcmpSendEcho) GetProcAddress(dll_, ICMP_SEND_FUNC);
+ if (!create_ || !close_ || !send_) {
+ LOG(LERROR) << "GetProcAddress(ICMP_*): " << GetLastError();
+ return;
+ }
+
+ hping_ = create_();
+ if (hping_ == INVALID_HANDLE_VALUE) {
+ LOG(LERROR) << "IcmpCreateFile: " << GetLastError();
+ return;
+ }
+
+ dlen_ = 0;
+ rlen_ = ReplySize(dlen_);
+ data_ = new char[dlen_];
+ reply_ = new char[rlen_];
+
+ valid_ = true;
+}
+
+WinPing::~WinPing() {
+ if (dll_)
+ FreeLibrary(dll_);
+
+ if ((hping_ != INVALID_HANDLE_VALUE) && close_) {
+ if (!close_(hping_))
+ LOG(WARNING) << "IcmpCloseHandle: " << GetLastError();
+ }
+
+ delete[] data_;
+ delete reply_;
+}
+
+WinPing::PingResult WinPing::Ping(
+ uint32 ip, uint32 data_size, uint32 timeout, uint8 ttl,
+ bool allow_fragments) {
+
+ assert(IsValid());
+
+ IP_OPTION_INFORMATION ipopt;
+ memset(&ipopt, 0, sizeof(ipopt));
+ if (!allow_fragments)
+ ipopt.Flags |= IP_FLAG_DF;
+ ipopt.Ttl = ttl;
+
+ uint32 reply_size = ReplySize(data_size);
+
+ if (data_size > dlen_) {
+ delete [] data_;
+ dlen_ = data_size;
+ data_ = new char[dlen_];
+ memset(data_, 'z', dlen_);
+ }
+
+ if (reply_size > rlen_) {
+ delete [] reply_;
+ rlen_ = reply_size;
+ reply_ = new char[rlen_];
+ }
+
+ DWORD result = send_(hping_, talk_base::HostToNetwork32(ip),
+ data_, uint16(data_size), &ipopt,
+ reply_, reply_size, timeout);
+ if (result == 0) {
+ long error = GetLastError();
+ if (error == IP_PACKET_TOO_BIG)
+ return PING_TOO_LARGE;
+ if (error == IP_REQ_TIMED_OUT)
+ return PING_TIMEOUT;
+ LOG(LERROR) << "IcmpSendEcho(" << talk_base::SocketAddress::IPToString(ip)
+ << ", " << data_size << "): " << error;
+ return PING_FAIL;
+ }
+
+ return PING_SUCCESS;
+}
+
+//////////////////////////////////////////////////////////////////////
+// Microsoft Documenation
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+// IcmpCreateFile
+//
+// Routine Description:
+//
+// Opens a handle on which ICMP Echo Requests can be issued.
+//
+// Arguments:
+//
+// None.
+//
+// Return Value:
+//
+// An open file handle or INVALID_HANDLE_VALUE. Extended error information
+// is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+// IcmpCloseHandle
+//
+// Routine Description:
+//
+// Closes a handle opened by ICMPOpenFile.
+//
+// Arguments:
+//
+// IcmpHandle - The handle to close.
+//
+// Return Value:
+//
+// TRUE if the handle was closed successfully, otherwise FALSE. Extended
+// error information is available by calling GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+//
+// Routine Name:
+//
+// IcmpSendEcho
+//
+// Routine Description:
+//
+// Sends an ICMP Echo request and returns any replies. The
+// call returns when the timeout has expired or the reply buffer
+// is filled.
+//
+// Arguments:
+//
+// IcmpHandle - An open handle returned by ICMPCreateFile.
+//
+// DestinationAddress - The destination of the echo request.
+//
+// RequestData - A buffer containing the data to send in the
+// request.
+//
+// RequestSize - The number of bytes in the request data buffer.
+//
+// RequestOptions - Pointer to the IP header options for the request.
+// May be NULL.
+//
+// ReplyBuffer - A buffer to hold any replies to the request.
+// On return, the buffer will contain an array of
+// ICMP_ECHO_REPLY structures followed by the
+// options and data for the replies. The buffer
+// should be large enough to hold at least one
+// ICMP_ECHO_REPLY structure plus
+// MAX(RequestSize, 8) bytes of data since an ICMP
+// error message contains 8 bytes of data.
+//
+// ReplySize - The size in bytes of the reply buffer.
+//
+// Timeout - The time in milliseconds to wait for replies.
+//
+// Return Value:
+//
+// Returns the number of ICMP_ECHO_REPLY structures stored in ReplyBuffer.
+// The status of each reply is contained in the structure. If the return
+// value is zero, extended error information is available via
+// GetLastError().
+//
+//////////////////////////////////////////////////////////////////////
+
+} // namespace talk_base
diff --git a/talk/base/winping.h b/talk/base/winping.h
new file mode 100644
index 0000000..35d36e3
--- /dev/null
+++ b/talk/base/winping.h
@@ -0,0 +1,97 @@
+/*
+ * 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_WINPING_H__
+#define TALK_BASE_WINPING_H__
+
+#ifdef WIN32
+
+#include "talk/base/win32.h"
+#include "talk/base/basictypes.h"
+
+namespace talk_base {
+
+// This class wraps a Win32 API for doing ICMP pinging. This API, unlike the
+// the normal socket APIs (as implemented on Win9x), will return an error if
+// an ICMP packet with the dont-fragment bit set is too large. This means this
+// class can be used to detect the MTU to a given address.
+
+typedef struct ip_option_information {
+ UCHAR Ttl; // Time To Live
+ UCHAR Tos; // Type Of Service
+ UCHAR Flags; // IP header flags
+ UCHAR OptionsSize; // Size in bytes of options data
+ PUCHAR OptionsData; // Pointer to options data
+} IP_OPTION_INFORMATION, * PIP_OPTION_INFORMATION;
+
+typedef HANDLE (WINAPI *PIcmpCreateFile)();
+
+typedef BOOL (WINAPI *PIcmpCloseHandle)(HANDLE icmp_handle);
+
+typedef DWORD (WINAPI *PIcmpSendEcho)(
+ HANDLE IcmpHandle,
+ ULONG DestinationAddress,
+ LPVOID RequestData,
+ WORD RequestSize,
+ PIP_OPTION_INFORMATION RequestOptions,
+ LPVOID ReplyBuffer,
+ DWORD ReplySize,
+ DWORD Timeout);
+
+class WinPing {
+public:
+ WinPing();
+ ~WinPing();
+
+ // Determines whether the class was initialized correctly.
+ bool IsValid() { return valid_; }
+
+ // Attempts to send a ping with the given parameters.
+ enum PingResult { PING_FAIL, PING_TOO_LARGE, PING_TIMEOUT, PING_SUCCESS };
+ PingResult Ping(
+ uint32 ip, uint32 data_size, uint32 timeout_millis, uint8 ttl,
+ bool allow_fragments);
+
+private:
+ HMODULE dll_;
+ HANDLE hping_;
+ PIcmpCreateFile create_;
+ PIcmpCloseHandle close_;
+ PIcmpSendEcho send_;
+ char* data_;
+ uint32 dlen_;
+ char* reply_;
+ uint32 rlen_;
+ bool valid_;
+};
+
+} // namespace talk_base
+
+#endif // WIN32
+
+#endif // TALK_BASE_WINPING_H__
+
diff --git a/talk/examples/call/call_main.cc b/talk/examples/call/call_main.cc
new file mode 100644
index 0000000..4312399
--- /dev/null
+++ b/talk/examples/call/call_main.cc
@@ -0,0 +1,391 @@
+/*
+ * 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 <time.h>
+#include <iomanip>
+#include <cstdio>
+#include <cstring>
+#include <vector>
+#include "talk/base/logging.h"
+#include "talk/base/flags.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/base/ssladapter.h"
+#include "talk/base/win32socketserver.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/examples/login/xmppauth.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/call/callclient.h"
+#include "talk/examples/call/console.h"
+#include "talk/session/phone/filemediaengine.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+class DebugLog : public sigslot::has_slots<> {
+ public:
+ DebugLog() :
+ debug_input_buf_(NULL), debug_input_len_(0), debug_input_alloc_(0),
+ debug_output_buf_(NULL), debug_output_len_(0), debug_output_alloc_(0),
+ censor_password_(false)
+ {}
+ char * debug_input_buf_;
+ int debug_input_len_;
+ int debug_input_alloc_;
+ char * debug_output_buf_;
+ int debug_output_len_;
+ int debug_output_alloc_;
+ bool censor_password_;
+
+ void Input(const char * data, int len) {
+ if (debug_input_len_ + len > debug_input_alloc_) {
+ char * old_buf = debug_input_buf_;
+ debug_input_alloc_ = 4096;
+ while (debug_input_alloc_ < debug_input_len_ + len) {
+ debug_input_alloc_ *= 2;
+ }
+ debug_input_buf_ = new char[debug_input_alloc_];
+ memcpy(debug_input_buf_, old_buf, debug_input_len_);
+ delete[] old_buf;
+ }
+ memcpy(debug_input_buf_ + debug_input_len_, data, len);
+ debug_input_len_ += len;
+ DebugPrint(debug_input_buf_, &debug_input_len_, false);
+ }
+
+ void Output(const char * data, int len) {
+ if (debug_output_len_ + len > debug_output_alloc_) {
+ char * old_buf = debug_output_buf_;
+ debug_output_alloc_ = 4096;
+ while (debug_output_alloc_ < debug_output_len_ + len) {
+ debug_output_alloc_ *= 2;
+ }
+ debug_output_buf_ = new char[debug_output_alloc_];
+ memcpy(debug_output_buf_, old_buf, debug_output_len_);
+ delete[] old_buf;
+ }
+ memcpy(debug_output_buf_ + debug_output_len_, data, len);
+ debug_output_len_ += len;
+ DebugPrint(debug_output_buf_, &debug_output_len_, true);
+ }
+
+ static bool IsAuthTag(const char * str, size_t len) {
+ if (str[0] == '<' && str[1] == 'a' &&
+ str[2] == 'u' &&
+ str[3] == 't' &&
+ str[4] == 'h' &&
+ str[5] <= ' ') {
+ std::string tag(str, len);
+
+ if (tag.find("mechanism") != std::string::npos)
+ return true;
+ }
+ return false;
+ }
+
+ void DebugPrint(char * buf, int * plen, bool output) {
+ int len = *plen;
+ if (len > 0) {
+ time_t tim = time(NULL);
+ struct tm * now = localtime(&tim);
+ char *time_string = asctime(now);
+ if (time_string) {
+ size_t time_len = strlen(time_string);
+ if (time_len > 0) {
+ time_string[time_len-1] = 0; // trim off terminating \n
+ }
+ }
+ LOG(INFO) << (output ? "SEND >>>>>>>>>>>>>>>>" : "RECV <<<<<<<<<<<<<<<<")
+ << " : " << time_string;
+
+ bool indent;
+ int start = 0, nest = 3;
+ for (int i = 0; i < len; i += 1) {
+ if (buf[i] == '>') {
+ if ((i > 0) && (buf[i-1] == '/')) {
+ indent = false;
+ } else if ((start + 1 < len) && (buf[start + 1] == '/')) {
+ indent = false;
+ nest -= 2;
+ } else {
+ indent = true;
+ }
+
+ // Output a tag
+ LOG(INFO) << std::setw(nest) << " "
+ << std::string(buf + start, i + 1 - start);
+
+ if (indent)
+ nest += 2;
+
+ // Note if it's a PLAIN auth tag
+ if (IsAuthTag(buf + start, i + 1 - start)) {
+ censor_password_ = true;
+ }
+
+ // incr
+ start = i + 1;
+ }
+
+ if (buf[i] == '<' && start < i) {
+ if (censor_password_) {
+ LOG(INFO) << std::setw(nest) << " " << "## TEXT REMOVED ##";
+ censor_password_ = false;
+ } else {
+ LOG(INFO) << std::setw(nest) << " "
+ << std::string(buf + start, i - start);
+ }
+ start = i;
+ }
+ }
+ len = len - start;
+ memcpy(buf, buf + start, len);
+ *plen = len;
+ }
+ }
+};
+
+static DebugLog debug_log_;
+static const int DEFAULT_PORT = 5222;
+
+
+cricket::MediaEngine* CreateFileMediaEngine(const char* voice_in,
+ const char* voice_out,
+ const char* video_in,
+ const char* video_out) {
+ cricket::FileMediaEngine* file_media_engine = new cricket::FileMediaEngine;
+ // Set the RTP dump file names.
+ if (voice_in) {
+ file_media_engine->set_voice_input_filename(voice_in);
+ }
+ if (voice_out) {
+ file_media_engine->set_voice_output_filename(voice_out);
+ }
+ if (video_in) {
+ file_media_engine->set_video_input_filename(video_in);
+ }
+ if (video_out) {
+ file_media_engine->set_video_output_filename(video_out);
+ }
+
+ // Set voice and video codecs. TODO: The codecs actually depend on
+ // the the input voice and video streams.
+ std::vector<cricket::AudioCodec> voice_codecs;
+ voice_codecs.push_back(
+ cricket::AudioCodec(9, "G722", 16000, 0, 1, 0));
+ file_media_engine->set_voice_codecs(voice_codecs);
+ std::vector<cricket::VideoCodec> video_codecs;
+ video_codecs.push_back(
+ cricket::VideoCodec(97, "H264", 320, 240, 30, 0));
+ file_media_engine->set_video_codecs(video_codecs);
+
+ return file_media_engine;
+}
+
+int main(int argc, char **argv) {
+ // This app has three threads. The main thread will run the XMPP client,
+ // which will print to the screen in its own thread. A second thread
+ // will get input from the console, parse it, and pass the appropriate
+ // message back to the XMPP client's thread. A third thread is used
+ // by MediaSessionClient as its worker thread.
+
+ // define options
+ DEFINE_bool(a, false, "Turn on auto accept.");
+ DEFINE_bool(d, false, "Turn on debugging.");
+ DEFINE_string(
+ protocol, "hybrid",
+ "Initial signaling protocol to use: jingle, gingle, or hybrid.");
+ DEFINE_string(
+ secure, "disable",
+ "Disable or enable encryption: disable, enable, require.");
+ DEFINE_bool(testserver, false, "Use test server");
+ DEFINE_bool(plainserver, false, "Turn off tls and allow plain password.");
+ DEFINE_int(portallocator, 0, "Filter out unwanted connection types.");
+ DEFINE_string(filterhost, NULL, "Filter out the host from all candidates.");
+ DEFINE_string(pmuc, "groupchat.google.com", "The persistant muc domain.");
+ DEFINE_string(s, "talk.google.com", "The connection server to use.");
+ DEFINE_string(voiceinput, NULL, "RTP dump file for voice input.");
+ DEFINE_string(voiceoutput, NULL, "RTP dump file for voice output.");
+ DEFINE_string(videoinput, NULL, "RTP dump file for video input.");
+ DEFINE_string(videooutput, NULL, "RTP dump file for video output.");
+ DEFINE_bool(help, false, "Prints this message");
+
+ // parse options
+ FlagList::SetFlagsFromCommandLine(&argc, argv, true);
+ if (FLAG_help) {
+ FlagList::Print(NULL, false);
+ return 0;
+ }
+
+ bool auto_accept = FLAG_a;
+ bool debug = FLAG_d;
+ std::string protocol = FLAG_protocol;
+ bool test_server = FLAG_testserver;
+ bool plain_server = FLAG_plainserver;
+ int32 portallocator_flags = FLAG_portallocator;
+ std::string pmuc_domain = FLAG_pmuc;
+ std::string server = FLAG_s;
+ std::string secure = FLAG_secure;
+
+ cricket::SignalingProtocol initial_protocol = cricket::PROTOCOL_HYBRID;
+ if (protocol == "jingle") {
+ initial_protocol = cricket::PROTOCOL_JINGLE;
+ } else if (protocol == "gingle") {
+ initial_protocol = cricket::PROTOCOL_GINGLE;
+ } else if (protocol == "hybrid") {
+ initial_protocol = cricket::PROTOCOL_HYBRID;
+ } else {
+ printf("Invalid protocol. Must be jingle, gingle, or hybrid.\n");
+ return 1;
+ }
+
+ cricket::SecureMediaPolicy secure_policy = cricket::SEC_DISABLED;
+ if (secure == "disable") {
+ secure_policy = cricket::SEC_DISABLED;
+ } else if (secure == "enable") {
+ secure_policy = cricket::SEC_ENABLED;
+ } else if (secure == "require") {
+ secure_policy = cricket::SEC_REQUIRED;
+ } else {
+ printf("Invalid encryption. Must be enable, disable, or require.\n");
+ return 1;
+ }
+
+ // parse username and password, if present
+ buzz::Jid jid;
+ std::string username;
+ talk_base::InsecureCryptStringImpl pass;
+ if (argc > 1) {
+ username = argv[1];
+ if (argc > 2) {
+ pass.password() = argv[2];
+ }
+ }
+
+ if (debug)
+ talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE);
+
+ if (username.empty()) {
+ std::cout << "JID: ";
+ std::cin >> username;
+ }
+ if (username.find('@') == std::string::npos) {
+ username.append("@localhost");
+ }
+ jid = buzz::Jid(username);
+ if (!jid.IsValid() || jid.node() == "") {
+ printf("Invalid JID. JIDs should be in the form user@domain\n");
+ return 1;
+ }
+ if (pass.password().empty() && !test_server) {
+ Console::SetEcho(false);
+ std::cout << "Password: ";
+ std::cin >> pass.password();
+ Console::SetEcho(true);
+ std::cout << std::endl;
+ }
+
+ buzz::XmppClientSettings xcs;
+ xcs.set_user(jid.node());
+ xcs.set_resource("call");
+ xcs.set_host(jid.domain());
+ xcs.set_use_tls(!test_server);
+
+ if (plain_server) {
+ xcs.set_use_tls(false);
+ xcs.set_allow_plain(true);
+ }
+ if (test_server) {
+ pass.password() = jid.node();
+ xcs.set_allow_plain(true);
+ }
+ xcs.set_pass(talk_base::CryptString(pass));
+
+ std::string host;
+ int port;
+
+ int colon = server.find(':');
+ if (colon == -1) {
+ host = server;
+ port = DEFAULT_PORT;
+ } else {
+ host = server.substr(0, colon);
+ port = atoi(server.substr(colon + 1).c_str());
+ }
+
+ xcs.set_server(talk_base::SocketAddress(host, port));
+ printf("Logging in to %s as %s\n", server.c_str(), jid.Str().c_str());
+
+ talk_base::InitializeSSL();
+
+
+#if WIN32
+ // Need to pump messages on our main thread on Windows.
+ talk_base::Win32Thread w32_thread;
+ talk_base::ThreadManager::SetCurrent(&w32_thread);
+#endif
+ talk_base::Thread* main_thread = talk_base::Thread::Current();
+
+ XmppPump pump;
+ CallClient *client = new CallClient(pump.client());
+
+ if (FLAG_voiceinput || FLAG_voiceoutput ||
+ FLAG_videoinput || FLAG_videooutput) {
+ // If any dump file is specified, we use FileMediaEngine.
+ cricket::MediaEngine* engine = CreateFileMediaEngine(FLAG_voiceinput,
+ FLAG_voiceoutput,
+ FLAG_videoinput,
+ FLAG_videooutput);
+ // The engine will be released by the client later.
+ client->SetMediaEngine(engine);
+ }
+
+ Console *console = new Console(main_thread, client);
+ client->SetConsole(console);
+ client->SetAutoAccept(auto_accept);
+ client->SetPmucDomain(pmuc_domain);
+ client->SetPortAllocatorFlags(portallocator_flags);
+ client->SetAllowLocalIps(true);
+ client->SetInitialProtocol(initial_protocol);
+ client->SetSecurePolicy(secure_policy);
+ console->Start();
+
+ if (debug) {
+ pump.client()->SignalLogInput.connect(&debug_log_, &DebugLog::Input);
+ pump.client()->SignalLogOutput.connect(&debug_log_, &DebugLog::Output);
+ }
+
+ pump.DoLogin(xcs, new XmppSocket(true), NULL);
+ main_thread->Run();
+ pump.DoDisconnect();
+
+ console->Stop();
+ delete console;
+ delete client;
+
+ return 0;
+}
diff --git a/talk/examples/call/callclient.cc b/talk/examples/call/callclient.cc
new file mode 100644
index 0000000..94581e7
--- /dev/null
+++ b/talk/examples/call/callclient.cc
@@ -0,0 +1,922 @@
+/*
+ * 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 "talk/examples/call/callclient.h"
+
+#include <string>
+
+#include "talk/base/basicpacketsocketfactory.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/network.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/thread.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/presencepushtask.h"
+#include "talk/examples/call/presenceouttask.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+#include "talk/examples/call/mucinvitesendtask.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+#include "talk/examples/call/muc.h"
+#include "talk/examples/call/voicemailjidrequester.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/client/sessionmanagertask.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/constants.h"
+
+
+class NullRenderer : public cricket::VideoRenderer {
+ public:
+ explicit NullRenderer(const char* s) : s_(s) {}
+ private:
+ bool SetSize(int width, int height, int reserved) {
+ LOG(LS_INFO) << "Video size for " << s_ << ": " << width << "x" << height;
+ return true;
+ }
+ bool RenderFrame(const cricket::VideoFrame *frame) {
+ return true;
+ }
+ const char* s_;
+};
+
+namespace {
+
+const char* DescribeStatus(buzz::Status::Show show, const std::string& desc) {
+ switch (show) {
+ case buzz::Status::SHOW_XA: return desc.c_str();
+ case buzz::Status::SHOW_ONLINE: return "online";
+ case buzz::Status::SHOW_AWAY: return "away";
+ case buzz::Status::SHOW_DND: return "do not disturb";
+ case buzz::Status::SHOW_CHAT: return "ready to chat";
+ default: return "offline";
+ }
+}
+
+std::string GetWord(const std::vector<std::string>& words,
+ size_t index, const std::string& def) {
+ if (words.size() > index) {
+ return words[index];
+ } else {
+ return def;
+ }
+}
+
+int GetInt(const std::vector<std::string>& words, size_t index, int def) {
+ int val;
+ if (words.size() > index && talk_base::FromString(words[index], &val)) {
+ return val;
+ } else {
+ return def;
+ }
+}
+
+
+} // namespace
+
+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"
+"";
+
+const char* RECEIVE_COMMANDS =
+"Available commands:\n"
+"\n"
+" accept [bw] Accepts the incoming call and switches to it.\n"
+" reject Rejects the incoming call and stays with the current call.\n"
+" quit Quits the application.\n"
+"";
+
+const char* CONSOLE_COMMANDS =
+"Available commands:\n"
+"\n"
+" roster Prints the online friends from your roster.\n"
+" friend user Request to add a user to your roster.\n"
+" call [jid] [bw] Initiates a call to the user[/room] with the\n"
+" given JID and with optional bandwidth.\n"
+" vcall [jid] [bw] Initiates a video call to the user[/room] with\n"
+" the given JID and with optional bandwidth.\n"
+" voicemail [jid] Leave a voicemail for the user with the given JID.\n"
+" join [room] Joins a multi-user-chat.\n"
+" invite user [room] Invites a friend to a multi-user-chat.\n"
+" leave [room] Leaves a multi-user-chat.\n"
+" getdevs Prints the available media devices.\n"
+" quit Quits the application.\n"
+"";
+
+void CallClient::ParseLine(const std::string& line) {
+ std::vector<std::string> words;
+ int start = -1;
+ int state = 0;
+ for (int index = 0; index <= static_cast<int>(line.size()); ++index) {
+ if (state == 0) {
+ if (!isspace(line[index])) {
+ start = index;
+ state = 1;
+ }
+ } else {
+ ASSERT(state == 1);
+ ASSERT(start >= 0);
+ if (isspace(line[index])) {
+ std::string word(line, start, index - start);
+ words.push_back(word);
+ start = -1;
+ state = 0;
+ }
+ }
+ }
+
+ // Global commands
+ const std::string& command = GetWord(words, 0, "");
+ if (command == "quit") {
+ Quit();
+ } else if (call_ && incoming_call_) {
+ if (command == "accept") {
+ cricket::CallOptions options;
+ options.video_bandwidth = GetInt(words, 1, cricket::kAutoBandwidth);
+ Accept(options);
+ } else if (command == "reject") {
+ Reject();
+ } else {
+ console_->Print(RECEIVE_COMMANDS);
+ }
+ } else if (call_) {
+ if (command == "hangup") {
+ // TODO: do more shutdown here, move to Terminate()
+ call_->Terminate();
+ call_ = NULL;
+ session_ = NULL;
+ console_->SetPrompt(NULL);
+ } else if (command == "mute") {
+ call_->Mute(true);
+ } else if (command == "unmute") {
+ call_->Mute(false);
+ } else if ((command == "dtmf") && (words.size() == 2)) {
+ int ev = std::string("0123456789*#").find(words[1][0]);
+ call_->PressDTMF(ev);
+ } else {
+ console_->Print(CALL_COMMANDS);
+ }
+ } else {
+ if (command == "roster") {
+ PrintRoster();
+ } else if (command == "send") {
+ buzz::Jid jid(words[1]);
+ if (jid.IsValid()) {
+ last_sent_to_ = words[1];
+ SendChat(words[1], words[2]);
+ } else if (!last_sent_to_.empty()) {
+ SendChat(last_sent_to_, words[1]);
+ } else {
+ console_->Printf(
+ "Invalid JID. JIDs should be in the form user@domain\n");
+ }
+ } else if ((words.size() == 2) && (command == "friend")) {
+ InviteFriend(words[1]);
+ } else if (command == "call") {
+ std::string to = GetWord(words, 1, "");
+ MakeCallTo(to, cricket::CallOptions());
+ } else if (command == "vcall") {
+ std::string to = GetWord(words, 1, "");
+ int bandwidth = GetInt(words, 2, cricket::kAutoBandwidth);
+ cricket::CallOptions options;
+ options.is_video = true;
+ options.video_bandwidth = bandwidth;
+ MakeCallTo(to, options);
+ } else if (command == "join") {
+ JoinMuc(GetWord(words, 1, ""));
+ } else if ((words.size() >= 2) && (command == "invite")) {
+ InviteToMuc(words[1], GetWord(words, 2, ""));
+ } else if (command == "leave") {
+ LeaveMuc(GetWord(words, 1, ""));
+ } else if (command == "getdevs") {
+ GetDevices();
+ } else if ((words.size() == 2) && (command == "setvol")) {
+ SetVolume(words[1]);
+ } else if (command == "voicemail") {
+ CallVoicemail((words.size() >= 2) ? words[1] : "");
+ } else {
+ console_->Print(CONSOLE_COMMANDS);
+ }
+ }
+}
+
+CallClient::CallClient(buzz::XmppClient* xmpp_client)
+ : xmpp_client_(xmpp_client),
+ media_engine_(NULL),
+ media_client_(NULL),
+ call_(NULL),
+ incoming_call_(false),
+ auto_accept_(false),
+ pmuc_domain_("groupchat.google.com"),
+ local_renderer_(NULL),
+ remote_renderer_(NULL),
+ roster_(new RosterMap),
+ portallocator_flags_(0),
+ allow_local_ips_(false),
+ initial_protocol_(cricket::PROTOCOL_HYBRID),
+ secure_policy_(cricket::SEC_DISABLED) {
+ xmpp_client_->SignalStateChange.connect(this, &CallClient::OnStateChange);
+}
+
+CallClient::~CallClient() {
+ delete media_client_;
+ delete roster_;
+}
+
+const std::string CallClient::strerror(buzz::XmppEngine::Error err) {
+ switch (err) {
+ case buzz::XmppEngine::ERROR_NONE:
+ return "";
+ case buzz::XmppEngine::ERROR_XML:
+ return "Malformed XML or encoding error";
+ case buzz::XmppEngine::ERROR_STREAM:
+ return "XMPP stream error";
+ case buzz::XmppEngine::ERROR_VERSION:
+ return "XMPP version error";
+ case buzz::XmppEngine::ERROR_UNAUTHORIZED:
+ return "User is not authorized (Check your username and password)";
+ case buzz::XmppEngine::ERROR_TLS:
+ return "TLS could not be negotiated";
+ case buzz::XmppEngine::ERROR_AUTH:
+ return "Authentication could not be negotiated";
+ case buzz::XmppEngine::ERROR_BIND:
+ return "Resource or session binding could not be negotiated";
+ case buzz::XmppEngine::ERROR_CONNECTION_CLOSED:
+ return "Connection closed by output handler.";
+ case buzz::XmppEngine::ERROR_DOCUMENT_CLOSED:
+ return "Closed by </stream:stream>";
+ case buzz::XmppEngine::ERROR_SOCKET:
+ return "Socket error";
+ default:
+ return "Unknown error";
+ }
+}
+
+void CallClient::OnCallDestroy(cricket::Call* call) {
+ if (call == call_) {
+ if (remote_renderer_) {
+ delete remote_renderer_;
+ remote_renderer_ = NULL;
+ }
+ if (local_renderer_) {
+ delete local_renderer_;
+ local_renderer_ = NULL;
+ }
+ console_->SetPrompt(NULL);
+ console_->Print("call destroyed");
+ call_ = NULL;
+ session_ = NULL;
+ }
+}
+
+void CallClient::OnStateChange(buzz::XmppEngine::State state) {
+ switch (state) {
+ case buzz::XmppEngine::STATE_START:
+ console_->Print("connecting...");
+ break;
+
+ case buzz::XmppEngine::STATE_OPENING:
+ console_->Print("logging in...");
+ break;
+
+ case buzz::XmppEngine::STATE_OPEN:
+ console_->Print("logged in...");
+ InitPhone();
+ InitPresence();
+ break;
+
+ case buzz::XmppEngine::STATE_CLOSED:
+ buzz::XmppEngine::Error error = xmpp_client_->GetError(NULL);
+ console_->Print("logged out..." + strerror(error));
+ Quit();
+ }
+}
+
+void CallClient::InitPhone() {
+ std::string client_unique = xmpp_client_->jid().Str();
+ talk_base::InitRandom(client_unique.c_str(), client_unique.size());
+
+ worker_thread_ = new talk_base::Thread();
+ // The worker thread must be started here since initialization of
+ // the ChannelManager will generate messages that need to be
+ // dispatched by it.
+ worker_thread_->Start();
+
+ // TODO: It looks like we are leaking many
+ // objects. E.g. |network_manager_| and |socket_factory_| are never
+ // deleted.
+
+ network_manager_ = new talk_base::NetworkManager();
+ socket_factory_ = new talk_base::BasicPacketSocketFactory(worker_thread_);
+
+ // TODO: Decide if the relay address should be specified here.
+ talk_base::SocketAddress stun_addr("stun.l.google.com", 19302);
+ port_allocator_ = new cricket::BasicPortAllocator(
+ network_manager_, socket_factory_, stun_addr,
+ talk_base::SocketAddress(), talk_base::SocketAddress(),
+ talk_base::SocketAddress());
+
+ if (portallocator_flags_ != 0) {
+ port_allocator_->set_flags(portallocator_flags_);
+ }
+ session_manager_ = new cricket::SessionManager(
+ port_allocator_, worker_thread_);
+ session_manager_->SignalRequestSignaling.connect(
+ this, &CallClient::OnRequestSignaling);
+ session_manager_->SignalSessionCreate.connect(
+ this, &CallClient::OnSessionCreate);
+ session_manager_->OnSignalingReady();
+
+ session_manager_task_ =
+ new cricket::SessionManagerTask(xmpp_client_, session_manager_);
+ session_manager_task_->EnableOutgoingMessages();
+ session_manager_task_->Start();
+
+ if (!media_engine_) {
+ media_engine_ = cricket::MediaEngine::Create();
+ }
+
+ media_client_ = new cricket::MediaSessionClient(
+ xmpp_client_->jid(),
+ session_manager_,
+ media_engine_,
+ new cricket::DeviceManager());
+ media_client_->SignalCallCreate.connect(this, &CallClient::OnCallCreate);
+ media_client_->SignalDevicesChange.connect(this,
+ &CallClient::OnDevicesChange);
+ media_client_->set_secure(secure_policy_);
+}
+
+void CallClient::OnRequestSignaling() {
+ session_manager_->OnSignalingReady();
+}
+
+void CallClient::OnSessionCreate(cricket::Session* session, bool initiate) {
+ session->set_allow_local_ips(allow_local_ips_);
+ session->set_current_protocol(initial_protocol_);
+}
+
+void CallClient::OnCallCreate(cricket::Call* call) {
+ call->SignalSessionState.connect(this, &CallClient::OnSessionState);
+ if (call->video()) {
+ local_renderer_ = new NullRenderer("local");
+ remote_renderer_ = new NullRenderer("remote");
+ }
+}
+
+void CallClient::OnSessionState(cricket::Call* call,
+ cricket::BaseSession* session,
+ cricket::BaseSession::State state) {
+ if (state == cricket::Session::STATE_RECEIVEDINITIATE) {
+ buzz::Jid jid(session->remote_name());
+ console_->Printf("Incoming call from '%s'", jid.Str().c_str());
+ call_ = call;
+ session_ = session;
+ incoming_call_ = true;
+ cricket::CallOptions options;
+ if (auto_accept_) {
+ Accept(options);
+ }
+ } else if (state == cricket::Session::STATE_SENTINITIATE) {
+ console_->Print("calling...");
+ } else if (state == cricket::Session::STATE_RECEIVEDACCEPT) {
+ console_->Print("call answered");
+ } else if (state == cricket::Session::STATE_RECEIVEDREJECT) {
+ console_->Print("call not answered");
+ } else if (state == cricket::Session::STATE_INPROGRESS) {
+ console_->Print("call in progress");
+ } else if (state == cricket::Session::STATE_RECEIVEDTERMINATE) {
+ console_->Print("other side hung up");
+ }
+}
+
+void CallClient::InitPresence() {
+ presence_push_ = new buzz::PresencePushTask(xmpp_client_, this);
+ presence_push_->SignalStatusUpdate.connect(
+ this, &CallClient::OnStatusUpdate);
+ presence_push_->SignalMucJoined.connect(this, &CallClient::OnMucJoined);
+ presence_push_->SignalMucLeft.connect(this, &CallClient::OnMucLeft);
+ presence_push_->SignalMucStatusUpdate.connect(
+ this, &CallClient::OnMucStatusUpdate);
+ presence_push_->Start();
+
+ presence_out_ = new buzz::PresenceOutTask(xmpp_client_);
+ RefreshStatus();
+ presence_out_->Start();
+
+ muc_invite_recv_ = new buzz::MucInviteRecvTask(xmpp_client_);
+ muc_invite_recv_->SignalInviteReceived.connect(this,
+ &CallClient::OnMucInviteReceived);
+ muc_invite_recv_->Start();
+
+ muc_invite_send_ = new buzz::MucInviteSendTask(xmpp_client_);
+ muc_invite_send_->Start();
+
+ friend_invite_send_ = new buzz::FriendInviteSendTask(xmpp_client_);
+ friend_invite_send_->Start();
+}
+
+void CallClient::RefreshStatus() {
+ int media_caps = media_client_->GetCapabilities();
+ my_status_.set_jid(xmpp_client_->jid());
+ my_status_.set_available(true);
+ my_status_.set_show(buzz::Status::SHOW_ONLINE);
+ my_status_.set_priority(0);
+ my_status_.set_know_capabilities(true);
+ my_status_.set_pmuc_capability(true);
+ my_status_.set_phone_capability(
+ (media_caps & cricket::MediaEngine::AUDIO_RECV) != 0);
+ my_status_.set_video_capability(
+ (media_caps & cricket::MediaEngine::VIDEO_RECV) != 0);
+ my_status_.set_camera_capability(
+ (media_caps & cricket::MediaEngine::VIDEO_SEND) != 0);
+ my_status_.set_is_google_client(true);
+ my_status_.set_version("1.0.0.67");
+ presence_out_->Send(my_status_);
+}
+
+void CallClient::OnStatusUpdate(const buzz::Status& status) {
+ RosterItem item;
+ item.jid = status.jid();
+ item.show = status.show();
+ item.status = status.status();
+
+ std::string key = item.jid.Str();
+
+ if (status.available() && status.phone_capability()) {
+ console_->Printf("Adding to roster: %s", key.c_str());
+ (*roster_)[key] = item;
+ } else {
+ console_->Printf("Removing from roster: %s", key.c_str());
+ RosterMap::iterator iter = roster_->find(key);
+ if (iter != roster_->end())
+ roster_->erase(iter);
+ }
+}
+
+void CallClient::PrintRoster() {
+ console_->SetPrompting(false);
+ console_->Printf("Roster contains %d callable", roster_->size());
+ RosterMap::iterator iter = roster_->begin();
+ while (iter != roster_->end()) {
+ console_->Printf("%s - %s",
+ iter->second.jid.BareJid().Str().c_str(),
+ DescribeStatus(iter->second.show, iter->second.status));
+ iter++;
+ }
+ console_->SetPrompting(true);
+}
+
+void CallClient::SendChat(const std::string& to, const std::string msg) {
+ buzz::XmlElement* stanza = new buzz::XmlElement(buzz::QN_MESSAGE);
+ stanza->AddAttr(buzz::QN_TO, to);
+ stanza->AddAttr(buzz::QN_ID, talk_base::CreateRandomString(16));
+ stanza->AddAttr(buzz::QN_TYPE, "chat");
+ buzz::XmlElement* body = new buzz::XmlElement(buzz::QN_BODY);
+ body->SetBodyText(msg);
+ stanza->AddElement(body);
+
+ xmpp_client_->SendStanza(stanza);
+ delete stanza;
+}
+
+void CallClient::InviteFriend(const std::string& name) {
+ buzz::Jid jid(name);
+ if (!jid.IsValid() || jid.node() == "") {
+ console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
+ return;
+ }
+ // Note: for some reason the Buzz backend does not forward our presence
+ // subscription requests to the end user when that user is another call
+ // client as opposed to a Smurf user. Thus, in that scenario, you must
+ // run the friend command as the other user too to create the linkage
+ // (and you won't be notified to do so).
+ friend_invite_send_->Send(jid);
+ console_->Printf("Requesting to befriend %s.\n", name.c_str());
+}
+
+void CallClient::MakeCallTo(const std::string& name,
+ const cricket::CallOptions& given_options) {
+ // Copy so we can change .is_muc.
+ cricket::CallOptions options = given_options;
+
+ bool found = false;
+ options.is_muc = false;
+ buzz::Jid callto_jid(name);
+ buzz::Jid found_jid;
+ if (name.length() == 0 && mucs_.size() > 0) {
+ // if no name, and in a MUC, establish audio with the MUC
+ found_jid = mucs_.begin()->first;
+ found = true;
+ options.is_muc = true;
+ } else if (name[0] == '+') {
+ // if the first character is a +, assume it's a phone number
+ found_jid = callto_jid;
+ found = true;
+ } else if (callto_jid.resource() == "voicemail") {
+ // if the resource is /voicemail, allow that
+ found_jid = callto_jid;
+ found = true;
+ } else {
+ // otherwise, it's a friend
+ for (RosterMap::iterator iter = roster_->begin();
+ iter != roster_->end(); ++iter) {
+ if (iter->second.jid.BareEquals(callto_jid)) {
+ found = true;
+ found_jid = iter->second.jid;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (mucs_.count(callto_jid) == 1 &&
+ mucs_[callto_jid]->state() == buzz::Muc::MUC_JOINED) {
+ found = true;
+ found_jid = callto_jid;
+ options.is_muc = true;
+ }
+ }
+ }
+
+ if (found) {
+ console_->Printf("Found %s '%s'", options.is_muc ? "room" : "online friend",
+ found_jid.Str().c_str());
+ PlaceCall(found_jid, options);
+ } else {
+ console_->Printf("Could not find online friend '%s'", name.c_str());
+ }
+}
+
+void CallClient::PlaceCall(const buzz::Jid& jid,
+ const cricket::CallOptions& options) {
+ media_client_->SignalCallDestroy.connect(
+ this, &CallClient::OnCallDestroy);
+ if (!call_) {
+ call_ = media_client_->CreateCall();
+ console_->SetPrompt(jid.Str().c_str());
+ session_ = call_->InitiateSession(jid, options);
+ if (options.is_muc) {
+ // If people in this room are already in a call, must add all their
+ // streams.
+ buzz::Muc::MemberMap& members = mucs_[jid]->members();
+ for (buzz::Muc::MemberMap::iterator elem = members.begin();
+ elem != members.end();
+ ++elem) {
+ AddStream(elem->second.audio_src_id(), elem->second.video_src_id());
+ }
+ }
+ }
+ media_client_->SetFocus(call_);
+ if (call_->video()) {
+ call_->SetLocalRenderer(local_renderer_);
+ // TODO: Call this once for every different remote SSRC
+ // once we get to testing multiway video.
+ call_->SetVideoRenderer(session_, 0, remote_renderer_);
+ }
+}
+
+void CallClient::CallVoicemail(const std::string& name) {
+ buzz::Jid jid(name);
+ if (!jid.IsValid() || jid.node() == "") {
+ console_->Printf("Invalid JID. JIDs should be in the form user@domain\n");
+ return;
+ }
+ buzz::VoicemailJidRequester *request =
+ new buzz::VoicemailJidRequester(xmpp_client_, jid, my_status_.jid());
+ request->SignalGotVoicemailJid.connect(this,
+ &CallClient::OnFoundVoicemailJid);
+ request->SignalVoicemailJidError.connect(this,
+ &CallClient::OnVoicemailJidError);
+ request->Start();
+}
+
+void CallClient::OnFoundVoicemailJid(const buzz::Jid& to,
+ const buzz::Jid& voicemail) {
+ console_->Printf("Calling %s's voicemail.\n", to.Str().c_str());
+ PlaceCall(voicemail, cricket::CallOptions());
+}
+
+void CallClient::OnVoicemailJidError(const buzz::Jid& to) {
+ console_->Printf("Unable to voicemail %s.\n", to.Str().c_str());
+}
+
+void CallClient::AddStream(uint32 audio_src_id, uint32 video_src_id) {
+ if (audio_src_id || video_src_id) {
+ console_->Printf("Adding stream (%u, %u)\n", audio_src_id, video_src_id);
+ call_->AddStream(session_, audio_src_id, video_src_id);
+ }
+}
+
+void CallClient::RemoveStream(uint32 audio_src_id, uint32 video_src_id) {
+ if (audio_src_id || video_src_id) {
+ console_->Printf("Removing stream (%u, %u)\n", audio_src_id, video_src_id);
+ call_->RemoveStream(session_, audio_src_id, video_src_id);
+ }
+}
+
+void CallClient::Accept(const cricket::CallOptions& options) {
+ ASSERT(call_ && incoming_call_);
+ ASSERT(call_->sessions().size() == 1);
+ call_->AcceptSession(call_->sessions()[0], options);
+ media_client_->SetFocus(call_);
+ if (call_->video()) {
+ call_->SetLocalRenderer(local_renderer_);
+ // The client never does an accept for multiway, so this must be 1:1,
+ // so there's no SSRC.
+ call_->SetVideoRenderer(session_, 0, remote_renderer_);
+ }
+ incoming_call_ = false;
+}
+
+void CallClient::Reject() {
+ ASSERT(call_ && incoming_call_);
+ call_->RejectSession(call_->sessions()[0]);
+ incoming_call_ = false;
+}
+
+void CallClient::Quit() {
+ talk_base::Thread::Current()->Quit();
+}
+
+void CallClient::JoinMuc(const std::string& room) {
+ buzz::Jid room_jid;
+ if (room.length() > 0) {
+ room_jid = buzz::Jid(room);
+ } else {
+ // generate a GUID of the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX,
+ // for an eventual JID of private-chat-<GUID>@groupchat.google.com
+ char guid[37], guid_room[256];
+ for (size_t i = 0; i < ARRAY_SIZE(guid) - 1;) {
+ if (i == 8 || i == 13 || i == 18 || i == 23) {
+ guid[i++] = '-';
+ } else {
+ sprintf(guid + i, "%04x", rand());
+ i += 4;
+ }
+ }
+
+ talk_base::sprintfn(guid_room, ARRAY_SIZE(guid_room),
+ "private-chat-%s@%s", guid, pmuc_domain_.c_str());
+ room_jid = buzz::Jid(guid_room);
+ }
+
+ if (!room_jid.IsValid()) {
+ console_->Printf("Unable to make valid muc endpoint for %s", room.c_str());
+ return;
+ }
+
+ MucMap::iterator elem = mucs_.find(room_jid);
+ if (elem != mucs_.end()) {
+ console_->Printf("This MUC already exists.");
+ return;
+ }
+
+ buzz::Muc* muc = new buzz::Muc(room_jid, xmpp_client_->jid().node());
+ mucs_[room_jid] = muc;
+ presence_out_->SendDirected(muc->local_jid(), my_status_);
+}
+
+void CallClient::OnMucInviteReceived(const buzz::Jid& inviter,
+ const buzz::Jid& room,
+ const std::vector<buzz::AvailableMediaEntry>& avail) {
+
+ console_->Printf("Invited to join %s by %s.\n", room.Str().c_str(),
+ inviter.Str().c_str());
+ console_->Printf("Available media:\n");
+ if (avail.size() > 0) {
+ for (std::vector<buzz::AvailableMediaEntry>::const_iterator i =
+ avail.begin();
+ i != avail.end();
+ ++i) {
+ console_->Printf(" %s, %s\n",
+ buzz::AvailableMediaEntry::TypeAsString(i->type),
+ buzz::AvailableMediaEntry::StatusAsString(i->status));
+ }
+ } else {
+ console_->Printf(" None\n");
+ }
+ // We automatically join the room.
+ JoinMuc(room.Str());
+}
+
+void CallClient::OnMucJoined(const buzz::Jid& endpoint) {
+ MucMap::iterator elem = mucs_.find(endpoint);
+ ASSERT(elem != mucs_.end() &&
+ elem->second->state() == buzz::Muc::MUC_JOINING);
+
+ buzz::Muc* muc = elem->second;
+ muc->set_state(buzz::Muc::MUC_JOINED);
+ console_->Printf("Joined \"%s\"", muc->jid().Str().c_str());
+}
+
+void CallClient::OnMucStatusUpdate(const buzz::Jid& jid,
+ const buzz::MucStatus& status) {
+
+ // Look up this muc.
+ MucMap::iterator elem = mucs_.find(jid);
+ ASSERT(elem != mucs_.end() &&
+ elem->second->state() == buzz::Muc::MUC_JOINED);
+
+ buzz::Muc* muc = elem->second;
+
+ if (status.jid().IsBare() || status.jid() == muc->local_jid()) {
+ // We are only interested in status about other users.
+ return;
+ }
+
+ if (!status.available()) {
+ // User is leaving the room.
+ buzz::Muc::MemberMap::iterator elem =
+ muc->members().find(status.jid().resource());
+
+ ASSERT(elem != muc->members().end());
+
+ // If user had src-ids, they have the left the room without explicitly
+ // hanging-up; must tear down the stream if in a call to this room.
+ if (call_ && session_->remote_name() == muc->jid().Str()) {
+ RemoveStream(elem->second.audio_src_id(), elem->second.video_src_id());
+ }
+
+ // Remove them from the room.
+ muc->members().erase(elem);
+ } else {
+ // Either user has joined or something changed about them.
+ // Note: The [] operator here will create a new entry if it does not
+ // exist, which is what we want.
+ buzz::MucStatus& member_status(
+ muc->members()[status.jid().resource()]);
+ if (call_ && session_->remote_name() == muc->jid().Str()) {
+ // We are in a call to this muc. Must potentially update our streams.
+ // The following code will correctly update our streams regardless of
+ // whether the SSRCs have been removed, added, or changed and regardless
+ // of whether that has been done to both or just one. This relies on the
+ // fact that AddStream/RemoveStream do nothing for SSRC arguments that are
+ // zero.
+ uint32 remove_audio_src_id = 0;
+ uint32 remove_video_src_id = 0;
+ uint32 add_audio_src_id = 0;
+ uint32 add_video_src_id = 0;
+ if (member_status.audio_src_id() != status.audio_src_id()) {
+ remove_audio_src_id = member_status.audio_src_id();
+ add_audio_src_id = status.audio_src_id();
+ }
+ if (member_status.video_src_id() != status.video_src_id()) {
+ remove_video_src_id = member_status.video_src_id();
+ add_video_src_id = status.video_src_id();
+ }
+ // Remove the old SSRCs, if any.
+ RemoveStream(remove_audio_src_id, remove_video_src_id);
+ // Add the new SSRCs, if any.
+ AddStream(add_audio_src_id, add_video_src_id);
+ }
+ // Update the status. This will use the compiler-generated copy
+ // constructor, which is perfectly adequate for this class.
+ member_status = status;
+ }
+}
+
+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;
+ }
+
+ if (!room_jid.IsValid()) {
+ console_->Printf("Invalid MUC JID.");
+ return;
+ }
+
+ MucMap::iterator elem = mucs_.find(room_jid);
+ if (elem == mucs_.end()) {
+ console_->Printf("No such MUC.");
+ return;
+ }
+
+ buzz::Muc* muc = elem->second;
+ muc->set_state(buzz::Muc::MUC_LEAVING);
+
+ buzz::Status status;
+ status.set_jid(my_status_.jid());
+ status.set_available(false);
+ status.set_priority(0);
+ presence_out_->SendDirected(muc->local_jid(), status);
+}
+
+void CallClient::OnMucLeft(const buzz::Jid& endpoint, int error) {
+ // We could be kicked from a room from any state. We would hope this
+ // happens While in the MUC_LEAVING state
+ MucMap::iterator elem = mucs_.find(endpoint);
+ if (elem == mucs_.end())
+ return;
+
+ buzz::Muc* muc = elem->second;
+ if (muc->state() == buzz::Muc::MUC_JOINING) {
+ console_->Printf("Failed to join \"%s\", code=%d",
+ muc->jid().Str().c_str(), error);
+ } else if (muc->state() == buzz::Muc::MUC_JOINED) {
+ console_->Printf("Kicked from \"%s\"",
+ muc->jid().Str().c_str());
+ }
+
+ delete muc;
+ mucs_.erase(elem);
+}
+
+void CallClient::InviteToMuc(const std::string& user, const std::string& room) {
+ // First find the room.
+ const buzz::Muc* found_muc;
+ if (room.length() == 0) {
+ if (mucs_.size() == 0) {
+ console_->Printf("Not in a room yet; can't invite.\n");
+ return;
+ }
+ // Invite to the first muc
+ found_muc = mucs_.begin()->second;
+ } else {
+ MucMap::iterator elem = mucs_.find(buzz::Jid(room));
+ if (elem == mucs_.end()) {
+ console_->Printf("Not in room %s.\n", room.c_str());
+ return;
+ }
+ found_muc = elem->second;
+ }
+ // Now find the user. We invite all of their resources.
+ bool found_user = false;
+ buzz::Jid user_jid(user);
+ for (RosterMap::iterator iter = roster_->begin();
+ iter != roster_->end(); ++iter) {
+ if (iter->second.jid.BareEquals(user_jid)) {
+ muc_invite_send_->Send(iter->second.jid, *found_muc);
+ found_user = true;
+ }
+ }
+ if (!found_user) {
+ console_->Printf("No such friend as %s.\n", user.c_str());
+ return;
+ }
+}
+
+void CallClient::GetDevices() {
+ std::vector<std::string> names;
+ media_client_->GetAudioInputDevices(&names);
+ printf("Audio input devices:\n");
+ PrintDevices(names);
+ media_client_->GetAudioOutputDevices(&names);
+ printf("Audio output devices:\n");
+ PrintDevices(names);
+ media_client_->GetVideoCaptureDevices(&names);
+ printf("Video capture devices:\n");
+ PrintDevices(names);
+}
+
+void CallClient::PrintDevices(const std::vector<std::string>& names) {
+ for (size_t i = 0; i < names.size(); ++i) {
+ printf("%d: %s\n", static_cast<int>(i), names[i].c_str());
+ }
+}
+
+void CallClient::OnDevicesChange() {
+ printf("Devices changed.\n");
+ RefreshStatus();
+}
+
+void CallClient::SetVolume(const std::string& level) {
+ media_client_->SetOutputVolume(strtol(level.c_str(), NULL, 10));
+}
diff --git a/talk/examples/call/callclient.h b/talk/examples/call/callclient.h
new file mode 100644
index 0000000..d2b806e
--- /dev/null
+++ b/talk/examples/call/callclient.h
@@ -0,0 +1,202 @@
+/*
+ * 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_EXAMPLES_CALL_CALLCLIENT_H_
+#define TALK_EXAMPLES_CALL_CALLCLIENT_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/examples/call/status.h"
+#include "talk/examples/call/console.h"
+
+namespace buzz {
+class PresencePushTask;
+class PresenceOutTask;
+class MucInviteRecvTask;
+class MucInviteSendTask;
+class FriendInviteSendTask;
+class VoicemailJidRequester;
+class DiscoInfoQueryTask;
+class Muc;
+class Status;
+class MucStatus;
+struct AvailableMediaEntry;
+}
+
+namespace talk_base {
+class Thread;
+class NetworkManager;
+}
+
+namespace cricket {
+class PortAllocator;
+class MediaEngine;
+class MediaSessionClient;
+class Receiver;
+class Call;
+struct CallOptions;
+class SessionManagerTask;
+}
+
+struct RosterItem {
+ buzz::Jid jid;
+ buzz::Status::Show show;
+ std::string status;
+};
+
+class NullRenderer;
+
+class CallClient: public sigslot::has_slots<> {
+ public:
+ explicit CallClient(buzz::XmppClient* xmpp_client);
+ ~CallClient();
+
+ cricket::MediaSessionClient* media_client() const { return media_client_; }
+ void SetMediaEngine(cricket::MediaEngine* media_engine) {
+ media_engine_ = media_engine;
+ }
+ void SetAutoAccept(bool auto_accept) {
+ auto_accept_ = auto_accept;
+ }
+ void SetPmucDomain(const std::string &pmuc_domain) {
+ pmuc_domain_ = pmuc_domain;
+ }
+ void SetConsole(Console *console) {
+ console_ = console;
+ }
+
+ void ParseLine(const std::string &str);
+
+ void SendChat(const std::string& to, const std::string msg);
+ void InviteFriend(const std::string& user);
+ void JoinMuc(const std::string& room);
+ void InviteToMuc(const std::string& user, const std::string& room);
+ void LeaveMuc(const std::string& room);
+ void SetPortAllocatorFlags(uint32 flags) { portallocator_flags_ = flags; }
+ void SetAllowLocalIps(bool allow_local_ips) {
+ allow_local_ips_ = allow_local_ips;
+ }
+
+ void SetInitialProtocol(cricket::SignalingProtocol initial_protocol) {
+ initial_protocol_ = initial_protocol;
+ }
+
+ void SetSecurePolicy(cricket::SecureMediaPolicy secure_policy) {
+ secure_policy_ = secure_policy;
+ }
+
+
+ typedef std::map<buzz::Jid, buzz::Muc*> MucMap;
+
+ const MucMap& mucs() const {
+ return mucs_;
+ }
+
+ private:
+ void AddStream(uint32 audio_src_id, uint32 video_src_id);
+ void RemoveStream(uint32 audio_src_id, uint32 video_src_id);
+ void OnStateChange(buzz::XmppEngine::State state);
+
+ void InitPhone();
+ void InitPresence();
+ void RefreshStatus();
+ void OnRequestSignaling();
+ void OnSessionCreate(cricket::Session* session, bool initiate);
+ void OnCallCreate(cricket::Call* call);
+ void OnCallDestroy(cricket::Call* call);
+ void OnSessionState(cricket::Call* call,
+ cricket::BaseSession* session,
+ cricket::BaseSession::State state);
+ void OnStatusUpdate(const buzz::Status& status);
+ void OnMucInviteReceived(const buzz::Jid& inviter, const buzz::Jid& room,
+ const std::vector<buzz::AvailableMediaEntry>& avail);
+ 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 OnDevicesChange();
+ void OnFoundVoicemailJid(const buzz::Jid& to, const buzz::Jid& voicemail);
+ void OnVoicemailJidError(const buzz::Jid& to);
+
+ static const std::string strerror(buzz::XmppEngine::Error err);
+
+ void PrintRoster();
+ void MakeCallTo(const std::string& name, const cricket::CallOptions& options);
+ void PlaceCall(const buzz::Jid& jid, const cricket::CallOptions& options);
+ void CallVoicemail(const std::string& name);
+ void Accept(const cricket::CallOptions& options);
+ void Reject();
+ void Quit();
+
+ void GetDevices();
+ void PrintDevices(const std::vector<std::string>& names);
+
+ void SetVolume(const std::string& level);
+
+ typedef std::map<std::string, RosterItem> RosterMap;
+
+ Console *console_;
+ buzz::XmppClient* xmpp_client_;
+ talk_base::Thread* worker_thread_;
+ talk_base::NetworkManager* network_manager_;
+ talk_base::PacketSocketFactory* socket_factory_;
+ cricket::PortAllocator* port_allocator_;
+ cricket::SessionManager* session_manager_;
+ cricket::SessionManagerTask* session_manager_task_;
+ cricket::MediaEngine* media_engine_;
+ cricket::MediaSessionClient* media_client_;
+ MucMap mucs_;
+
+ cricket::Call* call_;
+ cricket::BaseSession *session_;
+ bool incoming_call_;
+ bool auto_accept_;
+ std::string pmuc_domain_;
+ NullRenderer* local_renderer_;
+ NullRenderer* remote_renderer_;
+
+ buzz::Status my_status_;
+ buzz::PresencePushTask* presence_push_;
+ buzz::PresenceOutTask* presence_out_;
+ buzz::MucInviteRecvTask* muc_invite_recv_;
+ buzz::MucInviteSendTask* muc_invite_send_;
+ buzz::FriendInviteSendTask* friend_invite_send_;
+ RosterMap* roster_;
+ uint32 portallocator_flags_;
+
+ bool allow_local_ips_;
+ cricket::SignalingProtocol initial_protocol_;
+ cricket::SecureMediaPolicy secure_policy_;
+ std::string last_sent_to_;
+};
+
+#endif // TALK_EXAMPLES_CALL_CALLCLIENT_H_
diff --git a/talk/examples/call/console.cc b/talk/examples/call/console.cc
new file mode 100644
index 0000000..0aa7a4f
--- /dev/null
+++ b/talk/examples/call/console.cc
@@ -0,0 +1,167 @@
+/*
+ * 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.
+ */
+
+#define _CRT_SECURE_NO_DEPRECATE 1
+
+#ifdef POSIX
+#include <unistd.h>
+#endif // POSIX
+#include <cassert>
+#include "talk/base/logging.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stringutils.h"
+#include "talk/examples/call/console.h"
+#include "talk/examples/call/callclient.h"
+
+#ifdef POSIX
+#include <signal.h>
+
+static void DoNothing(int unused) {}
+#endif
+
+Console::Console(talk_base::Thread *thread, CallClient *client) :
+ client_(client), client_thread_(thread),
+ console_thread_(new talk_base::Thread()), prompt_(std::string("call")),
+ prompting_(true) {
+}
+
+Console::~Console() {
+ Stop();
+}
+
+void Console::Start() {
+ if (!console_thread_.get()) {
+ // stdin was closed in Stop(), so we can't restart.
+ LOG(LS_ERROR) << "Cannot re-start";
+ return;
+ }
+ if (console_thread_->started()) {
+ LOG(LS_WARNING) << "Already started";
+ return;
+ }
+ console_thread_->Start();
+ console_thread_->Post(this, MSG_START);
+}
+
+void Console::Stop() {
+ if (console_thread_.get() && console_thread_->started()) {
+#ifdef WIN32
+ CloseHandle(GetStdHandle(STD_INPUT_HANDLE));
+#else
+ close(fileno(stdin));
+ // This forces the read() in fgets() to return with errno = EINTR. fgets()
+ // will retry the read() and fail, thus returning.
+ pthread_kill(console_thread_->GetPThread(), SIGUSR1);
+#endif
+ console_thread_->Stop();
+ console_thread_.reset();
+ }
+}
+
+void Console::SetEcho(bool on) {
+#ifdef WIN32
+ HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
+ if ((hIn == INVALID_HANDLE_VALUE) || (hIn == NULL))
+ return;
+
+ DWORD mode;
+ if (!GetConsoleMode(hIn, &mode))
+ return;
+
+ if (on) {
+ mode = mode | ENABLE_ECHO_INPUT;
+ } else {
+ mode = mode & ~ENABLE_ECHO_INPUT;
+ }
+
+ SetConsoleMode(hIn, mode);
+#else
+ int re;
+ if (on)
+ re = system("stty echo");
+ else
+ re = system("stty -echo");
+ if (-1 == re)
+ return;
+#endif
+}
+
+void Console::Print(const char* str) {
+ printf("\n%s", str);
+ if (prompting_)
+ printf("\n(%s) ", prompt_.c_str());
+}
+
+void Console::Print(const std::string& str) {
+ Print(str.c_str());
+}
+
+void Console::Printf(const char* format, ...) {
+ va_list ap;
+ va_start(ap, format);
+
+ char buf[4096];
+ int size = vsnprintf(buf, sizeof(buf), format, ap);
+ assert(size >= 0);
+ assert(size < static_cast<int>(sizeof(buf)));
+ buf[size] = '\0';
+ Print(buf);
+
+ va_end(ap);
+}
+
+void Console::RunConsole() {
+ char input_buffer[128];
+ while (fgets(input_buffer, sizeof(input_buffer), stdin) != NULL) {
+ client_thread_->Post(this, MSG_INPUT,
+ new talk_base::TypedMessageData<std::string>(input_buffer));
+ }
+}
+
+void Console::OnMessage(talk_base::Message *msg) {
+ switch (msg->message_id) {
+ case MSG_START:
+#ifdef POSIX
+ // Install a no-op signal so that we can abort RunConsole() by raising
+ // SIGUSR1.
+ struct sigaction act;
+ act.sa_handler = &DoNothing;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ if (sigaction(SIGUSR1, &act, NULL) < 0) {
+ LOG(LS_WARNING) << "Can't install signal";
+ }
+#endif
+ RunConsole();
+ break;
+ case MSG_INPUT:
+ talk_base::TypedMessageData<std::string> *data =
+ static_cast<talk_base::TypedMessageData<std::string>*>(msg->pdata);
+ client_->ParseLine(data->data());
+ break;
+ }
+}
diff --git a/talk/examples/call/console.h b/talk/examples/call/console.h
new file mode 100644
index 0000000..e2ba4f7
--- /dev/null
+++ b/talk/examples/call/console.h
@@ -0,0 +1,85 @@
+/*
+ * 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_EXAMPLES_CALL_CONSOLE_H_
+#define TALK_EXAMPLES_CALL_CONSOLE_H_
+
+#include <cstdio>
+
+#include "talk/base/thread.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/scoped_ptr.h"
+
+class CallClient;
+
+class Console : public talk_base::MessageHandler {
+ public:
+ Console(talk_base::Thread *thread, CallClient *client);
+ ~Console();
+
+ // Starts reading lines from the console and giving them to the CallClient.
+ void Start();
+ // Stops reading lines. Cannot be restarted.
+ void Stop();
+
+ virtual void OnMessage(talk_base::Message *msg);
+
+ void SetPrompt(const char *prompt) {
+ prompt_ = prompt ? std::string(prompt) : std::string("call");
+ }
+
+ void SetPrompting(bool prompting) {
+ prompting_ = prompting;
+ if (prompting)
+ printf("\n(%s) ", prompt_.c_str());
+ }
+
+ bool prompting() { return prompting_; }
+
+ void Print(const char* str);
+ void Print(const std::string& str);
+ void Printf(const char* format, ...);
+
+ static void SetEcho(bool on);
+
+ private:
+ enum {
+ MSG_START,
+ MSG_INPUT,
+ };
+
+ void RunConsole();
+ void ParseLine(std::string &str);
+
+ CallClient *client_;
+ talk_base::Thread *client_thread_;
+ talk_base::scoped_ptr<talk_base::Thread> console_thread_;
+ std::string prompt_;
+ bool prompting_;
+};
+
+#endif // TALK_EXAMPLES_CALL_CONSOLE_H_
diff --git a/talk/examples/call/discoitemsquerytask.cc b/talk/examples/call/discoitemsquerytask.cc
new file mode 100644
index 0000000..df6d96d
--- /dev/null
+++ b/talk/examples/call/discoitemsquerytask.cc
@@ -0,0 +1,97 @@
+/*
+ * 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 "talk/examples/call/discoitemsquerytask.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+
+namespace buzz {
+
+namespace {
+const int kDiscoItemsTimeout = 60;
+} // namespace
+
+DiscoItemsQueryTask::DiscoItemsQueryTask(Task* parent,
+ const std::string node,
+ const Jid& to)
+ : XmppTask(parent, XmppEngine::HL_SINGLE), node_(node) {
+ set_timeout_seconds(kDiscoItemsTimeout);
+ to_ = to;
+}
+
+int DiscoItemsQueryTask::ProcessStart() {
+ talk_base::scoped_ptr<XmlElement> get(MakeIq(STR_GET, to_, task_id()));
+
+ XmlElement* element = new XmlElement(QN_DISCO_ITEMS_QUERY, true);
+ element->AddAttr(QN_NODE, node_);
+
+ get->AddElement(element);
+
+ if (SendStanza(get.get()) != XMPP_RETURN_OK) {
+ SignalDiscoItemsError(to_, NULL);
+ return STATE_ERROR;
+ }
+
+ return STATE_RESPONSE;
+}
+
+int DiscoItemsQueryTask::ProcessResponse() {
+ const XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ bool success = false;
+ if (stanza->Attr(QN_TYPE) != STR_ERROR) {
+ const XmlElement* query = stanza->FirstNamed(QN_DISCO_ITEMS_QUERY);
+ if (query) {
+ SignalGotDiscoItems(to_, query);
+ success = true;
+ }
+ }
+
+ if (!success) {
+ SignalDiscoItemsError(to_, stanza->FirstNamed(QN_ERROR));
+ }
+
+ return STATE_DONE;
+}
+
+int DiscoItemsQueryTask::OnTimeout() {
+ SignalDiscoItemsError(to_, NULL);
+ return XmppTask::OnTimeout();
+}
+
+bool DiscoItemsQueryTask::HandleStanza(const XmlElement* stanza) {
+ if (!MatchResponseIq(stanza, to_, task_id()))
+ return false;
+ QueueStanza(stanza);
+ return true;
+
+}
+
+}
diff --git a/talk/examples/call/discoitemsquerytask.h b/talk/examples/call/discoitemsquerytask.h
new file mode 100644
index 0000000..6be8c35
--- /dev/null
+++ b/talk/examples/call/discoitemsquerytask.h
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+// Fires a disco items query, such as the following example:
+//
+// <iq type='get'
+// from='foo@gmail.com/asdf'
+// to='bar@google.com'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items'
+// node='blah '/>
+// </iq>
+//
+// Sample response:
+//
+// <iq type='result'
+// from=' hendriks@google.com'
+// to='rsturgell@google.com/asdf'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items '
+// node='blah'>
+// <item something='somethingelse'/>
+// </query>
+// </iq>
+
+
+#ifndef _DISCOITEMSQUERYTASK_H_
+#define _DISCOITEMSQUERYTASK_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class DiscoItemsQueryTask : public XmppTask {
+ public:
+ // TODO: Currently, this only supports one query stanza - we may eventually
+ // need it to support multiple
+ DiscoItemsQueryTask(Task* parent, const std::string node, const Jid& to);
+
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+
+ // On success, fires a signal with the jid we sent the query to and the inner
+ // XmlElement
+ sigslot::signal2<Jid, const XmlElement*> SignalGotDiscoItems;
+
+ // The XmlElement here is the error element under the error response. If the
+ // request just timed out then this will be NULL
+ sigslot::signal2<Jid, const XmlElement*> SignalDiscoItemsError;
+
+ protected:
+ virtual bool HandleStanza(const XmlElement* stanza);
+ virtual int OnTimeout();
+
+ private:
+ // The jid we're querying
+ Jid to_;
+ // The name of the node
+ const std::string node_;
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/friendinvitesendtask.cc b/talk/examples/call/friendinvitesendtask.cc
new file mode 100644
index 0000000..cdb0b2c
--- /dev/null
+++ b/talk/examples/call/friendinvitesendtask.cc
@@ -0,0 +1,76 @@
+/*
+ * 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 "talk/xmpp/constants.h"
+#include "talk/examples/call/friendinvitesendtask.h"
+
+namespace buzz {
+
+XmppReturnStatus
+FriendInviteSendTask::Send(const Jid& user) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ // Need to first add to roster, then subscribe to presence.
+ XmlElement* iq = new XmlElement(QN_IQ);
+ iq->AddAttr(QN_TYPE, STR_SET);
+ XmlElement* query = new XmlElement(QN_ROSTER_QUERY);
+ XmlElement* item = new XmlElement(QN_ROSTER_ITEM);
+ item->AddAttr(QN_JID, user.Str());
+ item->AddAttr(QN_NAME, user.node());
+ query->AddElement(item);
+ iq->AddElement(query);
+ QueueStanza(iq);
+
+ // Subscribe to presence
+ XmlElement* presence = new XmlElement(QN_PRESENCE);
+ presence->AddAttr(QN_TO, user.Str());
+ presence->AddAttr(QN_TYPE, STR_SUBSCRIBE);
+ XmlElement* invitation = new XmlElement(QN_INVITATION);
+ invitation->AddAttr(QN_INVITE_MESSAGE,
+ "I've been using Google Talk and thought you might like to try it out. "
+ "We can use it to call each other for free over the internet. Here's an "
+ "invitation to download Google Talk. Give it a try!");
+ presence->AddElement(invitation);
+ QueueStanza(presence);
+
+ return XMPP_RETURN_OK;
+}
+
+int
+FriendInviteSendTask::ProcessStart() {
+ const XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ if (SendStanza(stanza) != XMPP_RETURN_OK)
+ return STATE_ERROR;
+
+ return STATE_START;
+}
+
+}
diff --git a/talk/examples/call/friendinvitesendtask.h b/talk/examples/call/friendinvitesendtask.h
new file mode 100644
index 0000000..3f776bb
--- /dev/null
+++ b/talk/examples/call/friendinvitesendtask.h
@@ -0,0 +1,48 @@
+/*
+ * 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 _FRIENDINVITESENDTASK_H_
+#define _FRIENDINVITESENDTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class FriendInviteSendTask : public XmppTask {
+public:
+ FriendInviteSendTask(Task* parent) : XmppTask(parent) {}
+ virtual ~FriendInviteSendTask() {}
+
+ XmppReturnStatus Send(const Jid& user);
+
+ virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/muc.h b/talk/examples/call/muc.h
new file mode 100644
index 0000000..2f6df2e
--- /dev/null
+++ b/talk/examples/call/muc.h
@@ -0,0 +1,66 @@
+/*
+ * 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 _MUC_H_
+#define _MUC_H_
+
+#include <map>
+#include "talk/xmpp/jid.h"
+#include "talk/examples/call/status.h"
+
+namespace buzz {
+
+class Muc {
+ public:
+ Muc(const Jid& jid, const std::string& nick) : state_(MUC_JOINING),
+ jid_(jid), local_jid_(Jid(jid.Str() + "/" + nick)) {}
+ ~Muc() {};
+
+ enum State { MUC_JOINING, MUC_JOINED, MUC_LEAVING };
+ State state() const { return state_; }
+ void set_state(State state) { state_ = state; }
+ const Jid & jid() const { return jid_; }
+ const Jid & local_jid() const { return local_jid_; }
+
+ typedef std::map<std::string, MucStatus> MemberMap;
+
+ // All the intelligence about how to manage the members is in
+ // CallClient, so we completely expose the map.
+ MemberMap& members() {
+ return members_;
+ }
+
+private:
+ State state_;
+ Jid jid_;
+ Jid local_jid_;
+ MemberMap members_;
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/mucinviterecvtask.cc b/talk/examples/call/mucinviterecvtask.cc
new file mode 100644
index 0000000..061db74
--- /dev/null
+++ b/talk/examples/call/mucinviterecvtask.cc
@@ -0,0 +1,124 @@
+/*
+ * 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 "talk/xmpp/constants.h"
+#include "talk/examples/call/mucinviterecvtask.h"
+
+namespace buzz {
+
+const char* types[] = {
+ "unknown",
+ "audio",
+ "video",
+};
+
+const char* statuses[] = {
+ "unknown",
+ "sendrecv",
+ "sendonly",
+ "recvonly",
+ "inactive",
+};
+
+const char*
+AvailableMediaEntry::TypeAsString(type_t type) {
+ // The values of the constants have been chosen such that this is correct.
+ return types[type];
+}
+
+const char*
+AvailableMediaEntry::StatusAsString(status_t status) {
+ // The values of the constants have been chosen such that this is correct.
+ return statuses[status];
+}
+
+int bodytext_to_array_pos(const XmlElement* elem, const char* array[],
+ int len, int defval = -1) {
+ if (elem) {
+ const std::string& body(elem->BodyText());
+ for (int i = 0; i < len; ++i) {
+ if (body == array[i]) {
+ // Found it.
+ return i;
+ }
+ }
+ }
+ // If we get here, it's not any value in the array.
+ return defval;
+}
+
+bool
+MucInviteRecvTask::HandleStanza(const XmlElement* stanza) {
+ // Figuring out that we want to handle this is a lot of the work of
+ // actually handling it, so we handle it right here instead of queueing it.
+ const XmlElement* xstanza;
+ const XmlElement* invite;
+ if (stanza->Name() != QN_MESSAGE) return false;
+ xstanza = stanza->FirstNamed(QN_MUC_USER_X);
+ if (!xstanza) return false;
+ invite = xstanza->FirstNamed(QN_MUC_USER_INVITE);
+ if (!invite) return false;
+ // Else it's an invite and we definitely want to handle it. Parse the
+ // available-media, if any.
+ std::vector<AvailableMediaEntry> v;
+ const XmlElement* avail =
+ invite->FirstNamed(QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA);
+ if (avail) {
+ for (const XmlElement* entry = avail->FirstNamed(QN_GOOGLE_MUC_USER_ENTRY);
+ entry;
+ entry = entry->NextNamed(QN_GOOGLE_MUC_USER_ENTRY)) {
+ AvailableMediaEntry tmp;
+ // In the interest of debugging, we accept as much valid-looking data
+ // as we can.
+ tmp.label = atoi(entry->Attr(QN_LABEL).c_str());
+ tmp.type = static_cast<AvailableMediaEntry::type_t>(
+ bodytext_to_array_pos(
+ entry->FirstNamed(QN_GOOGLE_MUC_USER_TYPE),
+ types,
+ sizeof(types)/sizeof(const char*),
+ AvailableMediaEntry::TYPE_UNKNOWN));
+ tmp.status = static_cast<AvailableMediaEntry::status_t>(
+ bodytext_to_array_pos(
+ entry->FirstNamed(QN_GOOGLE_MUC_USER_STATUS),
+ statuses,
+ sizeof(statuses)/sizeof(const char*),
+ AvailableMediaEntry::STATUS_UNKNOWN));
+ v.push_back(tmp);
+ }
+ }
+ SignalInviteReceived(Jid(invite->Attr(QN_FROM)), Jid(stanza->Attr(QN_FROM)),
+ v);
+ return true;
+}
+
+int
+MucInviteRecvTask::ProcessStart() {
+ // We never queue anything so we are always blocked.
+ return STATE_BLOCKED;
+}
+
+}
diff --git a/talk/examples/call/mucinviterecvtask.h b/talk/examples/call/mucinviterecvtask.h
new file mode 100644
index 0000000..892b484
--- /dev/null
+++ b/talk/examples/call/mucinviterecvtask.h
@@ -0,0 +1,81 @@
+/*
+ * 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 _MUCINVITERECVTASK_H_
+#define _MUCINVITERECVTASK_H_
+
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+struct AvailableMediaEntry {
+ enum type_t {
+ // SIP defines other media types, but these are the only ones we use in
+ // multiway jingle.
+ // These numbers are important; see .cc file
+ TYPE_UNKNOWN = 0, // indicates invalid string
+ TYPE_AUDIO = 1,
+ TYPE_VIDEO = 2,
+ };
+
+ enum status_t {
+ // These numbers are important; see .cc file
+ STATUS_UNKNOWN = 0, // indicates invalid string
+ STATUS_SENDRECV = 1,
+ STATUS_SENDONLY = 2,
+ STATUS_RECVONLY = 3,
+ STATUS_INACTIVE = 4,
+ };
+
+ uint32 label;
+ type_t type;
+ status_t status;
+
+ static const char* TypeAsString(type_t type);
+ static const char* StatusAsString(status_t status);
+};
+
+class MucInviteRecvTask : public XmppTask {
+ public:
+ MucInviteRecvTask(Task* parent) : XmppTask(parent, XmppEngine::HL_TYPE) {}
+ virtual int ProcessStart();
+
+ // First arg is inviter's JID; second is MUC's JID.
+ sigslot::signal3<const Jid&, const Jid&, const std::vector<AvailableMediaEntry>& > SignalInviteReceived;
+
+ protected:
+ virtual bool HandleStanza(const XmlElement* stanza);
+
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/mucinvitesendtask.cc b/talk/examples/call/mucinvitesendtask.cc
new file mode 100644
index 0000000..efd9a81
--- /dev/null
+++ b/talk/examples/call/mucinvitesendtask.cc
@@ -0,0 +1,63 @@
+/*
+ * 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 "talk/examples/call/mucinvitesendtask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+MucInviteSendTask::Send(const Jid& user, const Muc& muc) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement* message = new XmlElement(QN_MESSAGE);
+ message->AddAttr(QN_TO, muc.jid().Str());
+ XmlElement* xstanza = new XmlElement(QN_MUC_USER_X);
+ XmlElement* invite = new XmlElement(QN_MUC_USER_INVITE);
+ invite->AddAttr(QN_TO, user.Str());
+ xstanza->AddElement(invite);
+ message->AddElement(xstanza);
+
+ QueueStanza(message);
+ return XMPP_RETURN_OK;
+}
+
+int
+MucInviteSendTask::ProcessStart() {
+ const XmlElement* stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ if (SendStanza(stanza) != XMPP_RETURN_OK)
+ return STATE_ERROR;
+
+ return STATE_START;
+}
+
+}
diff --git a/talk/examples/call/mucinvitesendtask.h b/talk/examples/call/mucinvitesendtask.h
new file mode 100644
index 0000000..18e0f99
--- /dev/null
+++ b/talk/examples/call/mucinvitesendtask.h
@@ -0,0 +1,49 @@
+/*
+ * 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 _MUCINVITESENDTASK_H_
+#define _MUCINVITESENDTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/examples/call/muc.h"
+
+namespace buzz {
+
+class MucInviteSendTask : public XmppTask {
+public:
+ MucInviteSendTask(Task* parent) : XmppTask(parent) {}
+ virtual ~MucInviteSendTask() {}
+
+ XmppReturnStatus Send(const Jid& user, const Muc& muc);
+
+ virtual int ProcessStart();
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/presenceouttask.cc b/talk/examples/call/presenceouttask.cc
new file mode 100644
index 0000000..ff3d91b
--- /dev/null
+++ b/talk/examples/call/presenceouttask.cc
@@ -0,0 +1,147 @@
+/*
+ * 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 <time.h>
+#include <sstream>
+#include "talk/base/stringencode.h"
+#include "talk/examples/call/presenceouttask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+
+namespace buzz {
+
+XmppReturnStatus
+PresenceOutTask::Send(const Status & s) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ QueueStanza(TranslateStatus(s));
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+PresenceOutTask::SendDirected(const Jid & j, const Status & s) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement * presence = TranslateStatus(s);
+ presence->AddAttr(QN_TO, j.Str());
+ QueueStanza(presence);
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus PresenceOutTask::SendProbe(const Jid & jid) {
+ if (GetState() != STATE_INIT && GetState() != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement * presence = new XmlElement(QN_PRESENCE);
+ presence->AddAttr(QN_TO, jid.Str());
+ presence->AddAttr(QN_TYPE, "probe");
+
+ QueueStanza(presence);
+ return XMPP_RETURN_OK;
+}
+
+int
+PresenceOutTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ if (SendStanza(stanza) != XMPP_RETURN_OK)
+ return STATE_ERROR;
+
+ return STATE_START;
+}
+
+XmlElement *
+PresenceOutTask::TranslateStatus(const Status & s) {
+ XmlElement * result = new XmlElement(QN_PRESENCE);
+ if (!s.available()) {
+ result->AddAttr(QN_TYPE, STR_UNAVAILABLE);
+ }
+ else {
+ if (s.show() != Status::SHOW_ONLINE && s.show() != Status::SHOW_OFFLINE) {
+ result->AddElement(new XmlElement(QN_SHOW));
+ switch (s.show()) {
+ default:
+ result->AddText(STR_SHOW_AWAY, 1);
+ break;
+ case Status::SHOW_XA:
+ result->AddText(STR_SHOW_XA, 1);
+ break;
+ case Status::SHOW_DND:
+ result->AddText(STR_SHOW_DND, 1);
+ break;
+ case Status::SHOW_CHAT:
+ result->AddText(STR_SHOW_CHAT, 1);
+ break;
+ }
+ }
+
+ result->AddElement(new XmlElement(QN_STATUS));
+ result->AddText(s.status(), 1);
+
+ std::string pri;
+ talk_base::ToString(s.priority(), &pri);
+
+ result->AddElement(new XmlElement(QN_PRIORITY));
+ result->AddText(pri, 1);
+
+ if (s.know_capabilities() && s.is_google_client()) {
+ result->AddElement(new XmlElement(QN_CAPS_C, true));
+ result->AddAttr(QN_NODE, GOOGLE_CLIENT_NODE, 1);
+ result->AddAttr(QN_VER, s.version(), 1);
+
+ std::string caps;
+ caps.append(s.phone_capability() ? "voice-v1" : "");
+ caps.append(s.pmuc_capability() ? " pmuc-v1" : "");
+ caps.append(s.video_capability() ? " video-v1" : "");
+ caps.append(s.camera_capability() ? " camera-v1" : "");
+
+ result->AddAttr(QN_EXT, caps, 1);
+ }
+
+ // Put the delay mark on the presence according to JEP-0091
+ {
+ result->AddElement(new XmlElement(kQnDelayX, true));
+
+ // This here is why we *love* the C runtime
+ time_t current_time_seconds;
+ time(¤t_time_seconds);
+ struct tm* current_time = gmtime(¤t_time_seconds);
+ char output[256];
+ strftime(output, ARRAY_SIZE(output), "%Y%m%dT%H:%M:%S", current_time);
+ result->AddAttr(kQnStamp, output, 1);
+ }
+ }
+
+ return result;
+}
+
+
+}
diff --git a/talk/examples/call/presenceouttask.h b/talk/examples/call/presenceouttask.h
new file mode 100644
index 0000000..36e7f15
--- /dev/null
+++ b/talk/examples/call/presenceouttask.h
@@ -0,0 +1,53 @@
+/*
+ * 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 _PRESENCEOUTTASK_H_
+#define _PRESENCEOUTTASK_H_
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/examples/call/status.h"
+
+namespace buzz {
+
+class PresenceOutTask : public XmppTask {
+public:
+ PresenceOutTask(Task * parent) : XmppTask(parent) {}
+ virtual ~PresenceOutTask() {}
+
+ XmppReturnStatus Send(const Status & s);
+ XmppReturnStatus SendDirected(const Jid & j, const Status & s);
+ XmppReturnStatus SendProbe(const Jid& jid);
+
+ virtual int ProcessStart();
+private:
+ XmlElement * TranslateStatus(const Status & s);
+};
+
+}
+
+#endif
diff --git a/talk/examples/call/presencepushtask.cc b/talk/examples/call/presencepushtask.cc
new file mode 100644
index 0000000..f820030
--- /dev/null
+++ b/talk/examples/call/presencepushtask.cc
@@ -0,0 +1,254 @@
+/*
+ * 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 "talk/examples/call/presencepushtask.h"
+
+#include "talk/base/stringencode.h"
+#include "talk/examples/call/muc.h"
+#include "talk/xmpp/constants.h"
+
+
+
+namespace buzz {
+
+// string helper functions -----------------------------------------------------
+
+static bool
+IsXmlSpace(int ch) {
+ return ch == ' ' || ch == '\n' || ch == '\r' || ch == '\t';
+}
+
+static bool ListContainsToken(const std::string & list,
+ const std::string & token) {
+ size_t i = list.find(token);
+ if (i == std::string::npos || token.empty())
+ return false;
+ bool boundary_before = (i == 0 || IsXmlSpace(list[i - 1]));
+ bool boundary_after = (i == list.length() - token.length() ||
+ IsXmlSpace(list[i + token.length()]));
+ return boundary_before && boundary_after;
+}
+
+
+bool PresencePushTask::HandleStanza(const XmlElement * stanza) {
+ if (stanza->Name() != QN_PRESENCE)
+ return false;
+ QueueStanza(stanza);
+ return true;
+}
+
+static bool IsUtf8FirstByte(int c) {
+ return (((c)&0x80)==0) || // is single byte
+ ((unsigned char)((c)-0xc0)<0x3e); // or is lead byte
+}
+
+int PresencePushTask::ProcessStart() {
+ const XmlElement * stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+
+ Jid from(stanza->Attr(QN_FROM));
+ std::map<Jid, buzz::Muc*>::const_iterator elem =
+ client_->mucs().find(from.BareJid());
+ if (elem == client_->mucs().end()) {
+ HandlePresence(from, stanza);
+ } else {
+ HandleMucPresence(elem->second, from, stanza);
+ }
+
+ return STATE_START;
+}
+
+void PresencePushTask::HandlePresence(const Jid& from,
+ const XmlElement* stanza) {
+ if (stanza->Attr(QN_TYPE) == STR_ERROR)
+ return;
+
+ Status s;
+ FillStatus(from, stanza, &s);
+ SignalStatusUpdate(s);
+}
+
+void PresencePushTask::HandleMucPresence(buzz::Muc* muc,
+ const Jid& from,
+ const XmlElement* stanza) {
+ if (from == muc->local_jid()) {
+ if (!stanza->HasAttr(QN_TYPE)) {
+ // We joined the MUC.
+ const XmlElement* elem = stanza->FirstNamed(QN_MUC_USER_X);
+ if (elem) {
+ elem = elem->FirstNamed(QN_MUC_USER_STATUS);
+ }
+ if (elem && (elem->Attr(QN_CODE) == "110" ||
+ elem->Attr(QN_CODE) == "100")) {
+ SignalMucJoined(muc->jid());
+ }
+ } else {
+ // We've been kicked. Bye.
+ int error = 0;
+ if (stanza->Attr(QN_TYPE) == STR_ERROR) {
+ const XmlElement* elem = stanza->FirstNamed(QN_ERROR);
+ if (elem && elem->HasAttr(QN_CODE)) {
+ error = atoi(elem->Attr(QN_CODE).c_str());
+ }
+ }
+ SignalMucLeft(muc->jid(), error);
+ }
+ } else {
+ MucStatus s;
+ FillMucStatus(from, stanza, &s);
+ SignalMucStatusUpdate(muc->jid(), s);
+ }
+}
+
+void PresencePushTask::FillStatus(const Jid& from, const XmlElement* stanza,
+ Status* s) {
+ s->set_jid(from);
+ if (stanza->Attr(QN_TYPE) == STR_UNAVAILABLE) {
+ s->set_available(false);
+ } else {
+ s->set_available(true);
+ const XmlElement * status = stanza->FirstNamed(QN_STATUS);
+ if (status != NULL) {
+ s->set_status(status->BodyText());
+
+ // Truncate status messages longer than 300 bytes
+ if (s->status().length() > 300) {
+ size_t len = 300;
+
+ // Be careful not to split legal utf-8 chars in half
+ while (!IsUtf8FirstByte(s->status()[len]) && len > 0) {
+ len -= 1;
+ }
+ std::string truncated(s->status(), 0, len);
+ s->set_status(truncated);
+ }
+ }
+
+ const XmlElement * priority = stanza->FirstNamed(QN_PRIORITY);
+ if (priority != NULL) {
+ int pri;
+ if (talk_base::FromString(priority->BodyText(), &pri)) {
+ s->set_priority(pri);
+ }
+ }
+
+ const XmlElement * show = stanza->FirstNamed(QN_SHOW);
+ if (show == NULL || show->FirstChild() == NULL) {
+ s->set_show(Status::SHOW_ONLINE);
+ }
+ else {
+ if (show->BodyText() == "away") {
+ s->set_show(Status::SHOW_AWAY);
+ }
+ else if (show->BodyText() == "xa") {
+ s->set_show(Status::SHOW_XA);
+ }
+ else if (show->BodyText() == "dnd") {
+ s->set_show(Status::SHOW_DND);
+ }
+ else if (show->BodyText() == "chat") {
+ s->set_show(Status::SHOW_CHAT);
+ }
+ else {
+ s->set_show(Status::SHOW_ONLINE);
+ }
+ }
+
+ const XmlElement * caps = stanza->FirstNamed(QN_CAPS_C);
+ if (caps != NULL) {
+ std::string node = caps->Attr(QN_NODE);
+ std::string ver = caps->Attr(QN_VER);
+ std::string exts = caps->Attr(QN_EXT);
+
+ s->set_know_capabilities(true);
+
+ if (node == GOOGLE_CLIENT_NODE) {
+ s->set_is_google_client(true);
+ s->set_version(ver);
+ }
+
+ if (ListContainsToken(exts, "voice-v1")) {
+ s->set_phone_capability(true);
+ }
+ if (ListContainsToken(exts, "video-v1")) {
+ s->set_video_capability(true);
+ }
+ }
+
+ const XmlElement* delay = stanza->FirstNamed(kQnDelayX);
+ if (delay != NULL) {
+ // Ideally we would parse this according to the Psuedo ISO-8601 rules
+ // that are laid out in JEP-0082:
+ // http://www.jabber.org/jeps/jep-0082.html
+ std::string stamp = delay->Attr(kQnStamp);
+ s->set_sent_time(stamp);
+ }
+ }
+}
+
+void PresencePushTask::FillMucStatus(const Jid& from, const XmlElement* stanza,
+ MucStatus* s) {
+ // First get the normal user status info. Happily, this is in the same
+ // format as it is for user presence.
+ FillStatus(from, stanza, s);
+
+ // Now look for src IDs, which will be present if this user is in a
+ // multiway call to this MUC.
+ const XmlElement* xstanza = stanza->FirstNamed(QN_MUC_USER_X);
+ if (xstanza) {
+ const XmlElement* media;
+ for (media = xstanza->FirstNamed(QN_GOOGLE_MUC_USER_MEDIA);
+ media; media = media->NextNamed(QN_GOOGLE_MUC_USER_MEDIA)) {
+
+ const XmlElement* type = media->FirstNamed(QN_GOOGLE_MUC_USER_TYPE);
+ if (!type) continue; // Shouldn't happen
+
+ const XmlElement* src_id = media->FirstNamed(QN_GOOGLE_MUC_USER_SRC_ID);
+ if (!src_id) continue; // Shouldn't happen
+
+ char *endptr;
+ uint32 src_id_num = strtoul(src_id->BodyText().c_str(), &endptr, 10);
+ if (src_id->BodyText().c_str()[0] == '\0' || endptr[0] != '\0') {
+ // String is not composed exclusively of leading whitespace plus a
+ // number (shouldn't happen). Ignore it.
+ continue;
+ }
+ // Else it's valid. Set it.
+
+ if (type->BodyText() == "audio") {
+ // This is the audio media element. Get the src-id.
+ s->set_audio_src_id(src_id_num);
+ } else if (type->BodyText() == "video") {
+ // This is the video media element. Get the src-id.
+ s->set_video_src_id(src_id_num);
+ }
+ }
+ }
+}
+
+}
diff --git a/talk/examples/call/presencepushtask.h b/talk/examples/call/presencepushtask.h
new file mode 100644
index 0000000..998a98e
--- /dev/null
+++ b/talk/examples/call/presencepushtask.h
@@ -0,0 +1,70 @@
+/*
+ * 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 _PRESENCEPUSHTASK_H_
+#define _PRESENCEPUSHTASK_H_
+
+#include <vector>
+
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/base/sigslot.h"
+#include "talk/examples/call/status.h"
+#include "talk/examples/call/callclient.h"
+
+namespace buzz {
+
+class PresencePushTask : public XmppTask {
+ public:
+ PresencePushTask(Task * parent, CallClient* client)
+ : XmppTask(parent, XmppEngine::HL_TYPE),
+ client_(client) {}
+ virtual int ProcessStart();
+
+ sigslot::signal1<const Status&> SignalStatusUpdate;
+ sigslot::signal1<const Jid&> SignalMucJoined;
+ sigslot::signal2<const Jid&, int> SignalMucLeft;
+ sigslot::signal2<const Jid&, const MucStatus&> SignalMucStatusUpdate;
+
+ protected:
+ virtual bool HandleStanza(const XmlElement * stanza);
+ void HandlePresence(const Jid& from, const XmlElement * stanza);
+ void HandleMucPresence(buzz::Muc* muc,
+ const Jid& from, const XmlElement * stanza);
+ static void FillStatus(const Jid& from, const XmlElement * stanza,
+ Status* status);
+ static void FillMucStatus(const Jid& from, const XmlElement * stanza,
+ MucStatus* status);
+
+ private:
+ CallClient* client_;
+};
+
+
+}
+
+#endif
diff --git a/talk/examples/call/status.h b/talk/examples/call/status.h
new file mode 100644
index 0000000..be4c2bd
--- /dev/null
+++ b/talk/examples/call/status.h
@@ -0,0 +1,245 @@
+/*
+ * 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 _STATUS_H_
+#define _STATUS_H_
+
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/constants.h"
+
+#define GOOGLE_CLIENT_NODE "http://www.google.com/xmpp/client/caps"
+
+namespace buzz {
+
+class Status {
+public:
+ Status() :
+ pri_(0),
+ show_(SHOW_NONE),
+ available_(false),
+ e_code_(0),
+ feedback_probation_(false),
+ know_capabilities_(false),
+ phone_capability_(false),
+ pmuc_capability_(false),
+ video_capability_(false),
+ camera_capability_(false),
+ is_google_client_(false) {}
+
+ ~Status() {}
+
+ // These are arranged in "priority order", i.e., if we see
+ // two statuses at the same priority but with different Shows,
+ // we will show the one with the highest show in the following
+ // order.
+ enum Show {
+ SHOW_NONE = 0,
+ SHOW_OFFLINE = 1,
+ SHOW_XA = 2,
+ SHOW_AWAY = 3,
+ SHOW_DND = 4,
+ SHOW_ONLINE = 5,
+ SHOW_CHAT = 6,
+ };
+
+ const Jid & jid() const { return jid_; }
+ int priority() const { return pri_; }
+ Show show() const { return show_; }
+ const std::string & status() const { return status_; }
+ bool available() const { return available_ ; }
+ int error_code() const { return e_code_; }
+ const std::string & error_string() const { return e_str_; }
+ bool know_capabilities() const { return know_capabilities_; }
+ bool phone_capability() const { return phone_capability_; }
+ bool pmuc_capability() const { return pmuc_capability_; }
+ bool video_capability() const { return video_capability_; }
+ bool camera_capability() const { return camera_capability_; }
+ bool is_google_client() const { return is_google_client_; }
+ const std::string & version() const { return version_; }
+ bool feedback_probation() const { return feedback_probation_; }
+ const std::string& sent_time() const { return sent_time_; }
+
+ void set_jid(const Jid & jid) { jid_ = jid; }
+ void set_priority(int pri) { pri_ = pri; }
+ void set_show(Show show) { show_ = show; }
+ void set_status(const std::string & status) { status_ = status; }
+ void set_available(bool a) { available_ = a; }
+ void set_error(int e_code, const std::string e_str)
+ { e_code_ = e_code; e_str_ = e_str; }
+ void set_know_capabilities(bool f) { know_capabilities_ = f; }
+ void set_phone_capability(bool f) { phone_capability_ = f; }
+ void set_pmuc_capability(bool f) { pmuc_capability_ = f; }
+ void set_video_capability(bool f) { video_capability_ = f; }
+ void set_camera_capability(bool f) { camera_capability_ = f; }
+ void set_is_google_client(bool f) { is_google_client_ = f; }
+ void set_version(const std::string & v) { version_ = v; }
+ void set_feedback_probation(bool f) { feedback_probation_ = f; }
+ void set_sent_time(const std::string& time) { sent_time_ = time; }
+
+ void UpdateWith(const Status & new_value) {
+ if (!new_value.know_capabilities()) {
+ bool k = know_capabilities();
+ bool i = is_google_client();
+ bool p = phone_capability();
+ std::string v = version();
+
+ *this = new_value;
+
+ set_know_capabilities(k);
+ set_is_google_client(i);
+ set_phone_capability(p);
+ set_version(v);
+ }
+ else {
+ *this = new_value;
+ }
+ }
+
+ bool HasQuietStatus() const {
+ if (status_.empty())
+ return false;
+ return !(QuietStatus().empty());
+ }
+
+ // Knowledge of other clients' silly automatic status strings -
+ // Don't show these.
+ std::string QuietStatus() const {
+ if (jid_.resource().find("Psi") != std::string::npos) {
+ if (status_ == "Online" ||
+ status_.find("Auto Status") != std::string::npos)
+ return STR_EMPTY;
+ }
+ if (jid_.resource().find("Gaim") != std::string::npos) {
+ if (status_ == "Sorry, I ran out for a bit!")
+ return STR_EMPTY;
+ }
+ return TrimStatus(status_);
+ }
+
+ std::string ExplicitStatus() const {
+ std::string result = QuietStatus();
+ if (result.empty()) {
+ result = ShowStatus();
+ }
+ return result;
+ }
+
+ std::string ShowStatus() const {
+ std::string result;
+ if (!available()) {
+ result = "Offline";
+ }
+ else {
+ switch (show()) {
+ case SHOW_AWAY:
+ case SHOW_XA:
+ result = "Idle";
+ break;
+ case SHOW_DND:
+ result = "Busy";
+ break;
+ case SHOW_CHAT:
+ result = "Chatty";
+ break;
+ default:
+ result = "Available";
+ break;
+ }
+ }
+ return result;
+ }
+
+ static std::string TrimStatus(const std::string & st) {
+ std::string s(st);
+ int j = 0;
+ bool collapsing = true;
+ for (unsigned int i = 0; i < s.length(); i+= 1) {
+ if (s[i] <= ' ' && s[i] >= 0) {
+ if (collapsing) {
+ continue;
+ }
+ else {
+ s[j] = ' ';
+ j += 1;
+ collapsing = true;
+ }
+ }
+ else {
+ s[j] = s[i];
+ j += 1;
+ collapsing = false;
+ }
+ }
+ if (collapsing && j > 0) {
+ j -= 1;
+ }
+ s.erase(j, s.length());
+ return s;
+ }
+
+private:
+ Jid jid_;
+ int pri_;
+ Show show_;
+ std::string status_;
+ bool available_;
+ int e_code_;
+ std::string e_str_;
+ bool feedback_probation_;
+
+ // capabilities (valid only if know_capabilities_
+ bool know_capabilities_;
+ bool phone_capability_;
+ bool pmuc_capability_;
+ bool video_capability_;
+ bool camera_capability_;
+ bool is_google_client_;
+ std::string version_;
+
+ std::string sent_time_; // from the jabber:x:delay element
+};
+
+class MucStatus : public Status {
+public:
+ MucStatus() : audio_src_id_(0), video_src_id_(0) {}
+ uint32 audio_src_id() const { return audio_src_id_; }
+ uint32 video_src_id() const { return video_src_id_; }
+ void set_audio_src_id(uint32 audio_src_id) {
+ audio_src_id_ = audio_src_id;
+ }
+ void set_video_src_id(uint32 video_src_id) {
+ video_src_id_ = video_src_id;
+ }
+private:
+ uint32 audio_src_id_;
+ uint32 video_src_id_;
+};
+
+}
+
+
+#endif
diff --git a/talk/examples/call/voicemailjidrequester.cc b/talk/examples/call/voicemailjidrequester.cc
new file mode 100644
index 0000000..81f3dbc
--- /dev/null
+++ b/talk/examples/call/voicemailjidrequester.cc
@@ -0,0 +1,135 @@
+/*
+ * 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 "talk/examples/call/discoitemsquerytask.h"
+#include "talk/examples/call/voicemailjidrequester.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+VoicemailJidRequester::VoicemailJidRequester(talk_base::Task* parent,
+ const Jid& their_jid,
+ const Jid& my_jid) : Task(parent),
+ their_jid_(their_jid),
+ my_jid_(my_jid),
+ done_with_query_(false) {
+ parent_ = parent;
+}
+
+int VoicemailJidRequester::ProcessStart() {
+ // Start first query to node='voicemail'
+ DiscoItemsQueryTask* disco_items_task = new DiscoItemsQueryTask(this,
+ STR_VOICEMAIL, their_jid_.BareJid());
+ disco_items_task->SignalGotDiscoItems.connect(this,
+ &VoicemailJidRequester::OnFirstVoicemailJidSuccess);
+ disco_items_task->SignalDiscoItemsError.connect(this,
+ &VoicemailJidRequester::OnFirstVoicemailJidError);
+ disco_items_task->Start();
+ return STATE_BLOCKED;
+}
+
+void VoicemailJidRequester::OnFirstVoicemailJidError(buzz::Jid jid,
+ const XmlElement* xml_element) {
+ // First query gave us an error - try second query to node='outgoingvoicemail'
+ // and send it to your own jid
+ StartSecondQuery();
+}
+
+void VoicemailJidRequester::OnFirstVoicemailJidSuccess(buzz::Jid jid,
+ const XmlElement* xml_element) {
+ // Process the XML and fire the appropriate signals. If the xml was valid,
+ // then we're done with queries. If it wasn't valid, then start the second
+ // query.
+ bool valid_xml = ProcessVoicemailXml(xml_element);
+ if (valid_xml) {
+ done_with_query_ = true;
+ Wake();
+ } else {
+ StartSecondQuery();
+ }
+}
+
+void VoicemailJidRequester::OnSecondVoicemailJidError(buzz::Jid jid,
+ const XmlElement* xml_element) {
+ SignalVoicemailJidError(their_jid_);
+ done_with_query_ = true;
+ Wake();
+}
+
+void VoicemailJidRequester::OnSecondVoicemailJidSuccess(buzz::Jid jid,
+ const XmlElement* xml_element) {
+ // Whether this is good xml or bad, we're still done with the query
+ bool valid_xml = ProcessVoicemailXml(xml_element);
+ if (!valid_xml) {
+ SignalVoicemailJidError(their_jid_);
+ }
+ done_with_query_ = true;
+ Wake();
+}
+
+
+void VoicemailJidRequester::StartSecondQuery() {
+ // Send a query to your own jid to get the voicemail jid
+ DiscoItemsQueryTask* disco_items_task = new DiscoItemsQueryTask(this,
+ STR_OUTGOINGVOICEMAIL, my_jid_.BareJid());
+ disco_items_task->SignalGotDiscoItems.connect(this,
+ &VoicemailJidRequester::OnSecondVoicemailJidSuccess);
+ disco_items_task->SignalDiscoItemsError.connect(this,
+ &VoicemailJidRequester::OnSecondVoicemailJidError);
+ disco_items_task->Start();
+}
+
+int VoicemailJidRequester::Process(int state) {
+ if (done_with_query_) {
+ return STATE_DONE;
+ } else {
+ return talk_base::Task::Process(state);
+ }
+}
+
+bool VoicemailJidRequester::ProcessVoicemailXml(const XmlElement* xml_element) {
+ if (!xml_element) {
+ return false;
+ }
+ const std::string& node_name = xml_element->Attr(QN_NODE);
+ // Verify that it's one of the two nodes - we don't really care which one
+ if (node_name != "voicemail" &&
+ node_name != "outgoingvoicemail") {
+ return false;
+ }
+
+ const XmlElement* item = xml_element->FirstNamed(QN_DISCO_ITEM);
+ if (item) {
+ const std::string& jid_str = item->Attr(QN_JID);
+ buzz::Jid voicemail_jid(jid_str);
+ SignalGotVoicemailJid(their_jid_, voicemail_jid);
+ return true;
+ }
+ return false;
+}
+}
diff --git a/talk/examples/call/voicemailjidrequester.h b/talk/examples/call/voicemailjidrequester.h
new file mode 100644
index 0000000..34b3c4b
--- /dev/null
+++ b/talk/examples/call/voicemailjidrequester.h
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+// VoicemailJidRequester wraps the requesting of voicemail jids for a user.
+//
+// To request a voicemail jid, we first set off a query to the user's bare jid
+// that looks like this:
+//
+// <iq type='get'
+// from='foo@gmail.com/asdf'
+// to='bar@google.com'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items'
+// node='voicemail '/>
+// </iq>
+//
+// If foo@gmail.com's server supports voicemail, it'll return this, and forward
+// the jid up to phoneapp. We do not do the second query.
+//
+// <iq type='result'
+// from='foo@google.com'
+// to='bar@google.com/asdf'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items '
+// node=' voicemail '>
+// <item jid='bar@google.com/voicemail '/>
+// </query>
+// </iq>
+//
+// If we get an error, we spin off a new request:
+//
+// <iq type='get'
+// from='foo@google.com/asdf'
+// to='foo@google.com'
+// id='1234'>
+// <query xmlns=' http://jabber.org/protocol/disco#items'
+// node='outgoingvoicemail '/>
+// </iq>
+//
+// If both of these return errors, we then forward the request to phoneapp.
+
+#ifndef TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
+#define TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
+
+#include "talk/xmpp/xmpptask.h"
+
+namespace buzz {
+
+class Task;
+
+class VoicemailJidRequester : public sigslot::has_slots<>,
+ public talk_base::Task {
+ public:
+ VoicemailJidRequester(talk_base::Task* parent, const Jid& their_jid, const Jid& my_jid);
+
+ // Provides the target jid and the voicemail to reach it
+ sigslot::signal2<const Jid&, const Jid&> SignalGotVoicemailJid;
+ sigslot::signal1<const Jid&> SignalVoicemailJidError;
+
+ virtual int ProcessStart();
+ protected:
+
+ virtual int Process(int state);
+
+ private:
+ // The first query (to node='voicemail' has returned an error) - we now spin
+ // off a request to node='outgoingvoicemail')
+ void OnFirstVoicemailJidError(buzz::Jid jid, const XmlElement* xml_element);
+
+ // The first query (to node='voicemail' has returned a successfully)
+ void OnFirstVoicemailJidSuccess(buzz::Jid jid, const XmlElement* xml_element);
+
+ // The second query (to node='outgoingvoicemail') has returned an error -
+ // nothing we can do now, just fire our error signal
+ void OnSecondVoicemailJidError(buzz::Jid jid, const XmlElement* xml_element);
+
+ // The second query (to node='outgoingvoicemail') has returned a successfully
+ void OnSecondVoicemailJidSuccess(buzz::Jid jid,
+ const XmlElement* xml_element);
+
+ // Parse the xml, fire SignalGotVoicemail jid if it was valid (and had a jid)
+ // and return true if it was a valid xml.
+ bool ProcessVoicemailXml(const XmlElement* xml_element);
+
+ // Send a query to your own jid to get the voicemail jid. This is used after
+ // the first query fails.
+ void StartSecondQuery();
+
+ talk_base::Task* parent_;
+
+ Jid their_jid_;
+
+ // Your own jid (not the other user's)
+ Jid my_jid_;
+
+ // A flag indicating whether or not we're done with the query so that we can
+ // set the state correctly in Process(int state)
+ bool done_with_query_;
+};
+}
+
+#endif // TALK_EXAMPLES_CALL_VOICEMAILJIDREQUESTER_H_
diff --git a/talk/examples/login/login_main.cc b/talk/examples/login/login_main.cc
new file mode 100644
index 0000000..186020a
--- /dev/null
+++ b/talk/examples/login/login_main.cc
@@ -0,0 +1,64 @@
+/*
+ * 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 <cstdio>
+#include <iostream>
+
+#include "talk/base/thread.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+
+int main(int argc, char **argv) {
+ std::cout << "Auth Cookie: ";
+ std::string auth_cookie;
+ std::getline(std::cin, auth_cookie);
+
+ std::cout << "User Name: ";
+ std::string username;
+ std::getline(std::cin, username);
+
+ // Start xmpp on a different thread
+ XmppThread thread;
+ thread.Start();
+
+ buzz::XmppClientSettings xcs;
+ xcs.set_user(username.c_str());
+ xcs.set_host("gmail.com");
+ xcs.set_use_tls(false);
+ xcs.set_auth_cookie(auth_cookie.c_str());
+ xcs.set_server(talk_base::SocketAddress("talk.google.com", 5222));
+ thread.Login(xcs);
+
+ // Use main thread for console input
+ std::string line;
+ while (std::getline(std::cin, line)) {
+ if (line == "quit")
+ break;
+ }
+ return 0;
+}
+
diff --git a/talk/examples/login/xmppauth.cc b/talk/examples/login/xmppauth.cc
new file mode 100644
index 0000000..3551b97
--- /dev/null
+++ b/talk/examples/login/xmppauth.cc
@@ -0,0 +1,87 @@
+/*
+ * 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 "talk/examples/login/xmppauth.h"
+
+#include <algorithm>
+
+#include "talk/xmpp/saslcookiemechanism.h"
+#include "talk/xmpp/saslplainmechanism.h"
+
+XmppAuth::XmppAuth() : done_(false) {
+}
+
+XmppAuth::~XmppAuth() {
+}
+
+void XmppAuth::StartPreXmppAuth(const buzz::Jid & jid,
+ const talk_base::SocketAddress & server,
+ const talk_base::CryptString & pass,
+ const std::string & auth_cookie) {
+ jid_ = jid;
+ passwd_ = pass;
+ auth_cookie_ = auth_cookie;
+ done_ = true;
+
+ SignalAuthDone();
+}
+
+std::string XmppAuth::ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms,
+ bool encrypted) {
+ std::vector<std::string>::const_iterator it;
+
+ // a token is the weakest auth - 15s, service-limited, so prefer it.
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-TOKEN");
+ if (it != mechanisms.end() && !auth_cookie_.empty())
+ return "X-GOOGLE-TOKEN";
+
+ // a cookie is the next weakest - 14 days
+ it = std::find(mechanisms.begin(), mechanisms.end(), "X-GOOGLE-COOKIE");
+ if (it != mechanisms.end() && !auth_cookie_.empty())
+ return "X-GOOGLE-COOKIE";
+
+ it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it != mechanisms.end())
+ return "PLAIN";
+
+ // No good mechanism found
+ return "";
+}
+
+buzz::SaslMechanism* XmppAuth::CreateSaslMechanism(
+ const std::string & mechanism) {
+ if (mechanism == "X-GOOGLE-TOKEN") {
+ return new buzz::SaslCookieMechanism(mechanism, jid_.Str(), auth_cookie_);
+ //} else if (mechanism == "X-GOOGLE-COOKIE") {
+ // return new buzz::SaslCookieMechanism(mechanism, jid.Str(), sid_);
+ } else if (mechanism == "PLAIN") {
+ return new buzz::SaslPlainMechanism(jid_, passwd_);
+ } else {
+ return NULL;
+ }
+}
diff --git a/talk/examples/login/xmppauth.h b/talk/examples/login/xmppauth.h
new file mode 100644
index 0000000..18672b8
--- /dev/null
+++ b/talk/examples/login/xmppauth.h
@@ -0,0 +1,72 @@
+/*
+ * 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 _XMPPAUTH_H_
+#define _XMPPAUTH_H_
+
+#include <vector>
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslhandler.h"
+#include "talk/xmpp/prexmppauth.h"
+
+class XmppAuth: public buzz::PreXmppAuth {
+public:
+ XmppAuth();
+ virtual ~XmppAuth();
+
+ virtual void StartPreXmppAuth(const buzz::Jid & jid,
+ const talk_base::SocketAddress & server,
+ const talk_base::CryptString & pass,
+ const std::string & auth_cookie);
+
+ virtual bool IsAuthDone() const { return done_; }
+ virtual bool IsAuthorized() const { return true; }
+ virtual bool HadError() const { return false; }
+ virtual int GetError() const { return 0; }
+ virtual buzz::CaptchaChallenge GetCaptchaChallenge() const {
+ return buzz::CaptchaChallenge();
+ }
+ virtual std::string GetAuthCookie() const { return auth_cookie_; }
+
+ virtual std::string ChooseBestSaslMechanism(
+ const std::vector<std::string> & mechanisms,
+ bool encrypted);
+
+ virtual buzz::SaslMechanism * CreateSaslMechanism(
+ const std::string & mechanism);
+
+private:
+ buzz::Jid jid_;
+ talk_base::CryptString passwd_;
+ std::string auth_cookie_;
+ bool done_;
+};
+
+#endif
diff --git a/talk/examples/login/xmpppump.cc b/talk/examples/login/xmpppump.cc
new file mode 100644
index 0000000..de4057b
--- /dev/null
+++ b/talk/examples/login/xmpppump.cc
@@ -0,0 +1,78 @@
+/*
+ * 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 "talk/examples/login/xmpppump.h"
+#include "talk/examples/login/xmppauth.h"
+
+XmppPump::XmppPump(XmppPumpNotify * notify) {
+ state_ = buzz::XmppEngine::STATE_NONE;
+ notify_ = notify;
+ client_ = new buzz::XmppClient(this); // NOTE: deleted by TaskRunner
+}
+
+void XmppPump::DoLogin(const buzz::XmppClientSettings & xcs,
+ buzz::AsyncSocket* socket,
+ buzz::PreXmppAuth* auth) {
+ OnStateChange(buzz::XmppEngine::STATE_START);
+ if (!AllChildrenDone()) {
+ client_->SignalStateChange.connect(this, &XmppPump::OnStateChange);
+ client_->Connect(xcs, "", socket, auth);
+ client_->Start();
+ }
+}
+
+void XmppPump::DoDisconnect() {
+ if (!AllChildrenDone())
+ client_->Disconnect();
+ OnStateChange(buzz::XmppEngine::STATE_CLOSED);
+}
+
+void XmppPump::OnStateChange(buzz::XmppEngine::State state) {
+ if (state_ == state)
+ return;
+ state_ = state;
+ if (notify_ != NULL)
+ notify_->OnStateChange(state);
+}
+
+void XmppPump::WakeTasks() {
+ talk_base::Thread::Current()->Post(this);
+}
+
+int64 XmppPump::CurrentTime() {
+ return (int64)talk_base::Time();
+}
+
+void XmppPump::OnMessage(talk_base::Message *pmsg) {
+ RunTasks();
+}
+
+buzz::XmppReturnStatus XmppPump::SendStanza(const buzz::XmlElement *stanza) {
+ if (!AllChildrenDone())
+ return client_->SendStanza(stanza);
+ return buzz::XMPP_RETURN_BADSTATE;
+}
diff --git a/talk/examples/login/xmpppump.h b/talk/examples/login/xmpppump.h
new file mode 100644
index 0000000..4e79748
--- /dev/null
+++ b/talk/examples/login/xmpppump.h
@@ -0,0 +1,74 @@
+/*
+ * 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 _XMPPPUMP_H_
+#define _XMPPPUMP_H_
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/taskrunner.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+// Simple xmpp pump
+
+class XmppPumpNotify {
+public:
+ virtual ~XmppPumpNotify() {}
+ virtual void OnStateChange(buzz::XmppEngine::State state) = 0;
+};
+
+class XmppPump : public talk_base::MessageHandler, public talk_base::TaskRunner {
+public:
+ XmppPump(XmppPumpNotify * notify = NULL);
+
+ buzz::XmppClient *client() { return client_; }
+
+ void DoLogin(const buzz::XmppClientSettings & xcs,
+ buzz::AsyncSocket* socket,
+ buzz::PreXmppAuth* auth);
+ void DoDisconnect();
+
+ void OnStateChange(buzz::XmppEngine::State state);
+
+ void WakeTasks();
+
+ int64 CurrentTime();
+
+ void OnMessage(talk_base::Message *pmsg);
+
+ buzz::XmppReturnStatus SendStanza(const buzz::XmlElement *stanza);
+
+private:
+ buzz::XmppClient *client_;
+ buzz::XmppEngine::State state_;
+ XmppPumpNotify *notify_;
+};
+
+#endif // _XMPPPUMP_H_
diff --git a/talk/examples/login/xmppsocket.cc b/talk/examples/login/xmppsocket.cc
new file mode 100644
index 0000000..89e8662
--- /dev/null
+++ b/talk/examples/login/xmppsocket.cc
@@ -0,0 +1,249 @@
+/*
+ * 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+#include "talk/base/basicdefs.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/ssladapter.h"
+#endif
+#include "xmppsocket.h"
+
+#ifdef USE_SSLSTREAM
+#include "talk/base/socketstream.h"
+#ifdef FEATURE_ENABLE_SSL
+#include "talk/base/sslstreamadapter.h"
+#endif // FEATURE_ENABLE_SSL
+#endif // USE_SSLSTREAM
+
+XmppSocket::XmppSocket(bool tls) : tls_(tls) {
+ talk_base::Thread* pth = talk_base::Thread::Current();
+ talk_base::AsyncSocket* socket =
+ pth->socketserver()->CreateAsyncSocket(SOCK_STREAM);
+#ifndef USE_SSLSTREAM
+#ifdef FEATURE_ENABLE_SSL
+ if (tls_) {
+ socket = talk_base::SSLAdapter::Create(socket);
+ }
+#endif // FEATURE_ENABLE_SSL
+ cricket_socket_ = socket;
+ cricket_socket_->SignalReadEvent.connect(this, &XmppSocket::OnReadEvent);
+ cricket_socket_->SignalWriteEvent.connect(this, &XmppSocket::OnWriteEvent);
+ cricket_socket_->SignalConnectEvent.connect(this,
+ &XmppSocket::OnConnectEvent);
+ cricket_socket_->SignalCloseEvent.connect(this, &XmppSocket::OnCloseEvent);
+#else // USE_SSLSTREAM
+ cricket_socket_ = socket;
+ stream_ = new talk_base::SocketStream(cricket_socket_);
+#ifdef FEATURE_ENABLE_SSL
+ if (tls_)
+ stream_ = talk_base::SSLStreamAdapter::Create(stream_);
+#endif // FEATURE_ENABLE_SSL
+ stream_->SignalEvent.connect(this, &XmppSocket::OnEvent);
+#endif // USE_SSLSTREAM
+
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+}
+
+XmppSocket::~XmppSocket() {
+ Close();
+#ifndef USE_SSLSTREAM
+ delete cricket_socket_;
+#else // USE_SSLSTREAM
+ delete stream_;
+#endif // USE_SSLSTREAM
+}
+
+#ifndef USE_SSLSTREAM
+void XmppSocket::OnReadEvent(talk_base::AsyncSocket * socket) {
+ SignalRead();
+}
+
+void XmppSocket::OnWriteEvent(talk_base::AsyncSocket * socket) {
+ // Write bytes if there are any
+ while (buffer_.Length() != 0) {
+ int written = cricket_socket_->Send(buffer_.Data(), buffer_.Length());
+ if (written > 0) {
+ buffer_.Shift(written);
+ continue;
+ }
+ if (!cricket_socket_->IsBlocking())
+ LOG(LS_ERROR) << "Send error: " << cricket_socket_->GetError();
+ return;
+ }
+}
+
+void XmppSocket::OnConnectEvent(talk_base::AsyncSocket * socket) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+ state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+ SignalSSLConnected();
+ OnWriteEvent(cricket_socket_);
+ return;
+ }
+#endif // !defined(FEATURE_ENABLE_SSL)
+ state_ = buzz::AsyncSocket::STATE_OPEN;
+ SignalConnected();
+}
+
+void XmppSocket::OnCloseEvent(talk_base::AsyncSocket * socket, int error) {
+ SignalCloseEvent(error);
+}
+
+#else // USE_SSLSTREAM
+
+void XmppSocket::OnEvent(talk_base::StreamInterface* stream,
+ int events, int err) {
+ if ((events & talk_base::SE_OPEN)) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (state_ == buzz::AsyncSocket::STATE_TLS_CONNECTING) {
+ state_ = buzz::AsyncSocket::STATE_TLS_OPEN;
+ SignalSSLConnected();
+ events |= talk_base::SE_WRITE;
+ } else
+#endif
+ {
+ state_ = buzz::AsyncSocket::STATE_OPEN;
+ SignalConnected();
+ }
+ }
+ if ((events & talk_base::SE_READ))
+ SignalRead();
+ if ((events & talk_base::SE_WRITE)) {
+ // Write bytes if there are any
+ while (buffer_.Length() != 0) {
+ talk_base::StreamResult result;
+ size_t written;
+ int error;
+ result = stream_->Write(buffer_.Data(), buffer_.Length(),
+ &written, &error);
+ if (result == talk_base::SR_ERROR) {
+ LOG(LS_ERROR) << "Send error: " << error;
+ return;
+ }
+ if (result == talk_base::SR_BLOCK)
+ return;
+ ASSERT(result == talk_base::SR_SUCCESS);
+ ASSERT(written > 0);
+ buffer_.Shift(written);
+ }
+ }
+ if ((events & talk_base::SE_CLOSE))
+ SignalCloseEvent(err);
+}
+#endif // USE_SSLSTREAM
+
+buzz::AsyncSocket::State XmppSocket::state() {
+ return state_;
+}
+
+buzz::AsyncSocket::Error XmppSocket::error() {
+ return buzz::AsyncSocket::ERROR_NONE;
+}
+
+int XmppSocket::GetError() {
+ return 0;
+}
+
+bool XmppSocket::Connect(const talk_base::SocketAddress& addr) {
+ if (cricket_socket_->Connect(addr) < 0) {
+ return cricket_socket_->IsBlocking();
+ }
+ return true;
+}
+
+bool XmppSocket::Read(char * data, size_t len, size_t* len_read) {
+#ifndef USE_SSLSTREAM
+ int read = cricket_socket_->Recv(data, len);
+ if (read > 0) {
+ *len_read = (size_t)read;
+ return true;
+ }
+#else // USE_SSLSTREAM
+ talk_base::StreamResult result = stream_->Read(data, len, len_read, NULL);
+ if (result == talk_base::SR_SUCCESS)
+ return true;
+#endif // USE_SSLSTREAM
+ return false;
+}
+
+bool XmppSocket::Write(const char * data, size_t len) {
+ buffer_.WriteBytes(data, len);
+#ifndef USE_SSLSTREAM
+ OnWriteEvent(cricket_socket_);
+#else // USE_SSLSTREAM
+ OnEvent(stream_, talk_base::SE_WRITE, 0);
+#endif // USE_SSLSTREAM
+ return true;
+}
+
+bool XmppSocket::Close() {
+ if (state_ != buzz::AsyncSocket::STATE_OPEN)
+ return false;
+#ifndef USE_SSLSTREAM
+ if (cricket_socket_->Close() == 0) {
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+ SignalClosed();
+ return true;
+ }
+ return false;
+#else // USE_SSLSTREAM
+ state_ = buzz::AsyncSocket::STATE_CLOSED;
+ stream_->Close();
+ SignalClosed();
+ return true;
+#endif // USE_SSLSTREAM
+}
+
+bool XmppSocket::StartTls(const std::string & domainname) {
+#if defined(FEATURE_ENABLE_SSL)
+ if (!tls_)
+ return false;
+#ifndef USE_SSLSTREAM
+ talk_base::SSLAdapter* ssl_adapter =
+ static_cast<talk_base::SSLAdapter *>(cricket_socket_);
+ ssl_adapter->set_ignore_bad_cert(true);
+ if (ssl_adapter->StartSSL(domainname.c_str(), false) != 0)
+ return false;
+#else // USE_SSLSTREAM
+ talk_base::SSLStreamAdapter* ssl_stream =
+ static_cast<talk_base::SSLStreamAdapter *>(stream_);
+ ssl_stream->set_ignore_bad_cert(true);
+ if (ssl_stream->StartSSLWithServer(domainname.c_str()) != 0)
+ return false;
+#endif // USE_SSLSTREAM
+ state_ = buzz::AsyncSocket::STATE_TLS_CONNECTING;
+ return true;
+#else // !defined(FEATURE_ENABLE_SSL)
+ return false;
+#endif // !defined(FEATURE_ENABLE_SSL)
+}
diff --git a/talk/examples/login/xmppsocket.h b/talk/examples/login/xmppsocket.h
new file mode 100644
index 0000000..bce8ee3
--- /dev/null
+++ b/talk/examples/login/xmppsocket.h
@@ -0,0 +1,81 @@
+/*
+ * 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 _XMPPSOCKET_H_
+#define _XMPPSOCKET_H_
+
+#include "talk/base/asyncsocket.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/asyncsocket.h"
+
+// The below define selects the SSLStreamAdapter implementation for
+// SSL, as opposed to the SSLAdapter socket adapter.
+// #define USE_SSLSTREAM
+
+namespace talk_base {
+ class StreamInterface;
+};
+extern talk_base::AsyncSocket* cricket_socket_;
+
+class XmppSocket : public buzz::AsyncSocket, public sigslot::has_slots<> {
+public:
+ XmppSocket(bool tls);
+ ~XmppSocket();
+
+ virtual buzz::AsyncSocket::State state();
+ virtual buzz::AsyncSocket::Error error();
+ virtual int GetError();
+
+ virtual bool Connect(const talk_base::SocketAddress& addr);
+ virtual bool Read(char * data, size_t len, size_t* len_read);
+ virtual bool Write(const char * data, size_t len);
+ virtual bool Close();
+ virtual bool StartTls(const std::string & domainname);
+
+ sigslot::signal1<int> SignalCloseEvent;
+
+private:
+#ifndef USE_SSLSTREAM
+ void OnReadEvent(talk_base::AsyncSocket * socket);
+ void OnWriteEvent(talk_base::AsyncSocket * socket);
+ void OnConnectEvent(talk_base::AsyncSocket * socket);
+ void OnCloseEvent(talk_base::AsyncSocket * socket, int error);
+#else // USE_SSLSTREAM
+ void OnEvent(talk_base::StreamInterface* stream, int events, int err);
+#endif // USE_SSLSTREAM
+
+ talk_base::AsyncSocket * cricket_socket_;
+#ifdef USE_SSLSTREAM
+ talk_base::StreamInterface *stream_;
+#endif // USE_SSLSTREAM
+ buzz::AsyncSocket::State state_;
+ talk_base::ByteBuffer buffer_;
+ bool tls_;
+};
+
+#endif // _XMPPSOCKET_H_
diff --git a/talk/examples/login/xmppthread.cc b/talk/examples/login/xmppthread.cc
new file mode 100644
index 0000000..c030391
--- /dev/null
+++ b/talk/examples/login/xmppthread.cc
@@ -0,0 +1,80 @@
+/*
+ * 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 "talk/xmpp/xmppclientsettings.h"
+#include "talk/examples/login/xmppthread.h"
+#include "talk/examples/login/xmppauth.h"
+
+namespace {
+
+const uint32 MSG_LOGIN = 1;
+const uint32 MSG_DISCONNECT = 2;
+
+struct LoginData: public talk_base::MessageData {
+ LoginData(const buzz::XmppClientSettings& s) : xcs(s) {}
+ virtual ~LoginData() {}
+
+ buzz::XmppClientSettings xcs;
+};
+
+} // namespace
+
+XmppThread::XmppThread() {
+ pump_ = new XmppPump(this);
+}
+
+XmppThread::~XmppThread() {
+ delete pump_;
+}
+
+void XmppThread::ProcessMessages(int cms) {
+ talk_base::Thread::ProcessMessages(cms);
+}
+
+void XmppThread::Login(const buzz::XmppClientSettings& xcs) {
+ Post(this, MSG_LOGIN, new LoginData(xcs));
+}
+
+void XmppThread::Disconnect() {
+ Post(this, MSG_DISCONNECT);
+}
+
+void XmppThread::OnStateChange(buzz::XmppEngine::State state) {
+}
+
+void XmppThread::OnMessage(talk_base::Message* pmsg) {
+ if (pmsg->message_id == MSG_LOGIN) {
+ ASSERT(pmsg->pdata != NULL);
+ LoginData* data = reinterpret_cast<LoginData*>(pmsg->pdata);
+ pump_->DoLogin(data->xcs, new XmppSocket(false), new XmppAuth());
+ delete data;
+ } else if (pmsg->message_id == MSG_DISCONNECT) {
+ pump_->DoDisconnect();
+ } else {
+ ASSERT(false);
+ }
+}
diff --git a/talk/examples/login/xmppthread.h b/talk/examples/login/xmppthread.h
new file mode 100644
index 0000000..247b7bd
--- /dev/null
+++ b/talk/examples/login/xmppthread.h
@@ -0,0 +1,57 @@
+/*
+ * 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 _XMPPTHREAD_H_
+#define _XMPPTHREAD_H_
+
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/base/thread.h"
+#include "talk/examples/login/xmpppump.h"
+#include "talk/examples/login/xmppsocket.h"
+#include <iostream>
+
+class XmppThread:
+ public talk_base::Thread, XmppPumpNotify, talk_base::MessageHandler {
+public:
+ XmppThread();
+ ~XmppThread();
+
+ buzz::XmppClient* client() { return pump_->client(); }
+
+ void ProcessMessages(int cms);
+
+ void Login(const buzz::XmppClientSettings & xcs);
+ void Disconnect();
+
+private:
+ XmppPump* pump_;
+
+ void OnStateChange(buzz::XmppEngine::State state);
+ void OnMessage(talk_base::Message* pmsg);
+};
+
+#endif // _XMPPTHREAD_H_
diff --git a/talk/libjingle.scons b/talk/libjingle.scons
new file mode 100644
index 0000000..870a5d8
--- /dev/null
+++ b/talk/libjingle.scons
@@ -0,0 +1,313 @@
+import talk
+Import("env")
+
+talk.Library(env, name = "expat",
+ cppdefines = [
+ "XML_STATIC",
+ ],
+ srcs = [
+ "third_party/expat-2.0.1/lib/xmlparse.c",
+ "third_party/expat-2.0.1/lib/xmlrole.c",
+ "third_party/expat-2.0.1/lib/xmltok.c",
+ ],
+ includedirs = [
+ "third_party/expat-2.0.1/",
+ ],
+ win_cppdefines = [
+ "COMPILED_FROM_DSP",
+ ],
+ posix_cppdefines = [
+ "HAVE_EXPAT_CONFIG_H",
+ ],
+)
+talk.Library(env, name = "libsrtp",
+ srcs = [
+ "third_party/srtp/crypto/cipher/aes.c",
+ "third_party/srtp/crypto/cipher/aes_cbc.c",
+ "third_party/srtp/crypto/cipher/aes_icm.c",
+ "third_party/srtp/crypto/cipher/cipher.c",
+ "third_party/srtp/crypto/cipher/null_cipher.c",
+ "third_party/srtp/crypto/hash/auth.c",
+ "third_party/srtp/crypto/hash/hmac.c",
+ "third_party/srtp/crypto/hash/null_auth.c",
+ "third_party/srtp/crypto/hash/sha1.c",
+ "third_party/srtp/crypto/replay/rdb.c",
+ "third_party/srtp/crypto/replay/rdbx.c",
+ "third_party/srtp/crypto/replay/ut_sim.c",
+ "third_party/srtp/crypto/math/datatypes.c",
+ "third_party/srtp/crypto/math/stat.c",
+ "third_party/srtp/crypto/kernel/alloc.c",
+ "third_party/srtp/crypto/kernel/crypto_kernel.c",
+ "third_party/srtp/crypto/kernel/err.c",
+ "third_party/srtp/crypto/kernel/key.c",
+ "third_party/srtp/crypto/rng/ctr_prng.c",
+ "third_party/srtp/crypto/rng/rand_source.c",
+ "third_party/srtp/srtp/ekt.c",
+ "third_party/srtp/srtp/srtp.c",
+ ],
+ includedirs = [
+ "third_party/srtp/include",
+ "third_party/srtp/crypto/include",
+ ],
+ win_ccflags = [
+ "/wd4701",
+ "/wd4702",
+ ],
+)
+talk.Library(env, name = "libjingle",
+ lin_srcs = [
+ "base/latebindingsymboltable.cc",
+ "base/linux.cc",
+ "session/phone/libudevsymboltable.cc",
+ "session/phone/v4llookup.cc",
+ ],
+ mac_srcs = [
+ "base/macconversion.cc",
+ "base/macutils.cc",
+ "session/phone/devicemanager_mac.mm",
+ ],
+ posix_srcs = [
+ "base/unixfilesystem.cc",
+ "base/opensslidentity.cc",
+ "base/opensslstreamadapter.cc",
+ "base/sslidentity.cc",
+ "base/sslstreamadapter.cc",
+ ],
+ cppdefines = [
+ "FEATURE_ENABLE_VOICEMAIL",
+ "EXPAT_RELATIVE_PATH",
+ "SRTP_RELATIVE_PATH",
+ "XML_STATIC",
+ ],
+ srcs = [
+ "base/asyncfile.cc",
+ "base/asynchttprequest.cc",
+ "base/asyncsocket.cc",
+ "base/asynctcpsocket.cc",
+ "base/asyncudpsocket.cc",
+ "base/autodetectproxy.cc",
+ "base/base64.cc",
+ "base/basicpacketsocketfactory.cc",
+ "base/bytebuffer.cc",
+ "base/checks.cc",
+ "base/common.cc",
+ "base/diskcache.cc",
+ "base/event.cc",
+ "base/fileutils.cc",
+ "base/firewallsocketserver.cc",
+ "base/flags.cc",
+ "base/helpers.cc",
+ "base/host.cc",
+ "base/httpbase.cc",
+ "base/httpclient.cc",
+ "base/httpcommon.cc",
+ "base/httprequest.cc",
+ "base/logging.cc",
+ "base/md5c.c",
+ "base/messagehandler.cc",
+ "base/messagequeue.cc",
+ "base/nethelpers.cc",
+ "base/network.cc",
+ "base/openssladapter.cc",
+ "base/pathutils.cc",
+ "base/physicalsocketserver.cc",
+ "base/proxydetect.cc",
+ "base/proxyinfo.cc",
+ "base/ratetracker.cc",
+ "base/signalthread.cc",
+ "base/socketadapters.cc",
+ "base/socketaddress.cc",
+ "base/socketaddresspair.cc",
+ "base/socketpool.cc",
+ "base/socketstream.cc",
+ "base/ssladapter.cc",
+ "base/sslsocketfactory.cc",
+ "base/stream.cc",
+ "base/stringdigest.cc",
+ "base/stringencode.cc",
+ "base/stringutils.cc",
+ "base/task.cc",
+ "base/taskparent.cc",
+ "base/taskrunner.cc",
+ "base/thread.cc",
+ "base/time.cc",
+ "base/urlencode.cc",
+ "p2p/base/constants.cc",
+ "p2p/base/p2ptransport.cc",
+ "p2p/base/p2ptransportchannel.cc",
+ "p2p/base/parsing.cc",
+ "p2p/base/port.cc",
+ "p2p/base/pseudotcp.cc",
+ "p2p/base/relayport.cc",
+ "p2p/base/relayserver.cc",
+ "p2p/base/rawtransport.cc",
+ "p2p/base/rawtransportchannel.cc",
+ "p2p/base/session.cc",
+ "p2p/base/sessiondescription.cc",
+ "p2p/base/sessionmanager.cc",
+ "p2p/base/sessionmessages.cc",
+ "p2p/base/stun.cc",
+ "p2p/base/stunport.cc",
+ "p2p/base/stunrequest.cc",
+ "p2p/base/stunserver.cc",
+ "p2p/base/tcpport.cc",
+ "p2p/base/transport.cc",
+ "p2p/base/transportchannel.cc",
+ "p2p/base/transportchannelproxy.cc",
+ "p2p/base/udpport.cc",
+ "p2p/client/basicportallocator.cc",
+ "p2p/client/httpportallocator.cc",
+ "p2p/client/socketmonitor.cc",
+ "session/tunnel/pseudotcpchannel.cc",
+ "session/tunnel/tunnelsessionclient.cc",
+ "session/tunnel/securetunnelsessionclient.cc",
+ "session/phone/audiomonitor.cc",
+ "session/phone/call.cc",
+ "session/phone/channel.cc",
+ "session/phone/channelmanager.cc",
+ "session/phone/codec.cc",
+ "session/phone/devicemanager.cc",
+ "session/phone/filemediaengine.cc",
+ "session/phone/mediaengine.cc",
+ "session/phone/mediamonitor.cc",
+ "session/phone/mediasessionclient.cc",
+ "session/phone/rtpdump.cc",
+ "session/phone/rtcpmuxfilter.cc",
+ "session/phone/soundclip.cc",
+ "session/phone/srtpfilter.cc",
+ "xmllite/qname.cc",
+ "xmllite/xmlbuilder.cc",
+ "xmllite/xmlconstants.cc",
+ "xmllite/xmlelement.cc",
+ "xmllite/xmlnsstack.cc",
+ "xmllite/xmlparser.cc",
+ "xmllite/xmlprinter.cc",
+ "xmpp/constants.cc",
+ "xmpp/jid.cc",
+ "xmpp/ratelimitmanager.cc",
+ "xmpp/saslmechanism.cc",
+ "xmpp/xmppclient.cc",
+ "xmpp/xmppengineimpl.cc",
+ "xmpp/xmppengineimpl_iq.cc",
+ "xmpp/xmpplogintask.cc",
+ "xmpp/xmppstanzaparser.cc",
+ "xmpp/xmpptask.cc",
+ ],
+ includedirs = [
+ "third_party/libudev",
+ "third_party/expat-2.0.1/",
+ "third_party/srtp/include",
+ "third_party/srtp/crypto/include",
+ ],
+ win_srcs = [
+ "base/schanneladapter.cc",
+ "base/win32.cc",
+ "base/win32filesystem.cc",
+ "base/win32securityerrors.cc",
+ "base/win32socketserver.cc",
+ "base/win32socketinit.cc",
+ "base/win32window.cc",
+ "base/winfirewall.cc",
+ "base/winping.cc",
+ ],
+)
+talk.App(env, name = "login",
+ libs = [
+ "libjingle",
+ "expat",
+ "libxmpphelp",
+ ],
+ srcs = [
+ "examples/login/xmppthread.cc",
+ "examples/login/login_main.cc",
+ ],
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ lin_libs = [
+ "libpthread",
+ ":libssl.so.0.9.8",
+ ],
+)
+talk.Library(env, name = "libxmpphelp",
+ libs = [
+ "libjingle",
+ ],
+ srcs = [
+ "examples/login/xmppauth.cc",
+ "examples/login/xmpppump.cc",
+ "examples/login/xmppsocket.cc",
+ ],
+)
+talk.App(env, name = "call",
+ mac_frameworks = [
+ "AudioToolbox",
+ "AudioUnit",
+ "Cocoa",
+ "CoreAudio",
+ "CoreFoundation",
+ "IOKit",
+ "QTKit",
+ "QuickTime",
+ ],
+ win_libs = [
+ "d3d9.lib",
+ "gdi32.lib",
+ "powrprof.lib",
+ "strmiids.lib",
+ "winmm.lib",
+ ],
+ mac_libs = [
+ "crypto",
+ "ssl",
+ ],
+ cppdefines = [
+ "FEATURE_ENABLE_VOICEMAIL",
+ ],
+ lin_libs = [
+ "libasound",
+ "libpthread",
+ ":libssl.so.0.9.8",
+ ],
+ srcs = [
+ "examples/call/call_main.cc",
+ "examples/call/callclient.cc",
+ "examples/call/console.cc",
+ "examples/call/discoitemsquerytask.cc",
+ "examples/call/friendinvitesendtask.cc",
+ "examples/call/mucinviterecvtask.cc",
+ "examples/call/mucinvitesendtask.cc",
+ "examples/call/presenceouttask.cc",
+ "examples/call/presencepushtask.cc",
+ "examples/call/voicemailjidrequester.cc",
+ ],
+ libs = [
+ "libjingle",
+ "expat",
+ "libsrtp",
+ "libxmpphelp",
+ ],
+)
+talk.App(env, name = "relayserver",
+ libs = [
+ "libjingle",
+ ],
+ srcs = [
+ "p2p/base/relayserver_main.cc",
+ ],
+ lin_libs = [
+ "libpthread",
+ ],
+)
+talk.App(env, name = "stunserver",
+ libs = [
+ "libjingle",
+ ],
+ srcs = [
+ "p2p/base/stunserver_main.cc",
+ ],
+ lin_libs = [
+ "libpthread",
+ ],
+)
diff --git a/talk/main.scons b/talk/main.scons
new file mode 100644
index 0000000..b8705d1
--- /dev/null
+++ b/talk/main.scons
@@ -0,0 +1,496 @@
+# -*- Python -*-
+#
+# All the helper functions are defined in:
+# - site_scons/talk.py
+# Use 'import talk' in any .scons file to get access to it.
+# Add any new helper functions to it; unittest are available
+# in talk_unittest.py.
+#
+# Each 'component' that is built is defined in a .scons file.
+# See talk.Components(...) for further info on file naming convention.
+#
+# To add a new platform clone and modify the root_env object. Remember to add
+# the new environment object to the envs list otherwise it will not be included
+# in the build.
+#
+#
+#
+import talk
+import os
+import platform
+
+#-------------------------------------------------------------------------------
+# The build files/directories to 'build'.
+# If the name is the name of a directory then that directory shall contain a
+# .scons file with the same name as the directory itself:
+# Ex: The directory session/phone contains a file called phone.scons
+#
+components = talk.Components("libjingle.scons")
+
+#-------------------------------------------------------------------------------
+# Build environments
+#
+
+# The list of build environments.
+envs = []
+
+# The root of all builds.
+root_env = Environment(
+ tools = [
+ 'component_bits',
+ 'component_setup',
+ 'replace_strings',
+ 'talk_noops',
+ #'talk_linux',
+ ],
+ BUILD_SCONSCRIPTS = components,
+ DESTINATION_ROOT = '$MAIN_DIR/build',
+ CPPPATH = [
+ '$OBJ_ROOT', # generated headers are relative to here
+ '$MAIN_DIR/..', # TODO: how can we use GOOGLECLIENT instead?
+ ],
+ CPPDEFINES = [
+ # Temp flag while porting to hammer.
+ 'HAMMER_TIME=1',
+ 'LOGGING=1',
+
+ # Feature selection
+ 'FEATURE_ENABLE_SSL',
+ 'FEATURE_ENABLE_VOICEMAIL',
+ 'FEATURE_ENABLE_PSTN',
+ 'HAVE_SRTP',
+ ]
+)
+
+# This is where we set common environments
+#
+# Detect if running on 64 bit or 32 bit host.
+DeclareBit('platform_arch_64bit', 'Host Platform is 64 Bit')
+if platform.architecture()[0] == "64bit":
+ root_env.SetBits('platform_arch_64bit')
+
+DeclareBit('use_static_openssl', 'Build OpenSSL as a static library')
+
+
+#-------------------------------------------------------------------------------
+# W I N D O W S
+#
+win_env = root_env.Clone(
+ tools = [
+ 'atlmfc_vc80',
+ #'code_signing',
+ 'component_targets_msvs',
+ 'directx_9_0_c',
+ #'grid_builder',
+ 'midl',
+ 'target_platform_windows',
+ ],
+ # Don't use default vc80 midl.exe. It doesn't understand vista_sdk idl files.
+ MIDL = '$PLATFORM_SDK_VISTA_6_0_DIR/Bin/midl.exe ',
+ WIX_DIR = '$GOOGLECLIENT/third_party/wix/v3_0_2925/files',
+ # Flags for debug and optimization are added to CCFLAGS instead
+ CCPDBFLAGS = '',
+ CCFLAGS_DEBUG = '',
+ CCFLAGS_OPTIMIZED = '',
+ # We force a x86 target even when building on x64 Windows platforms.
+ TARGET_ARCH = 'x86',
+)
+
+win_env.Append(
+ COMPONENT_LIBRARY_PUBLISH = True, # Put dlls in output dir too
+ CCFLAGS = [
+ '/Fd${TARGET}.pdb', # pdb per object allows --jobs=
+ '/WX', # warnings are errors
+ '/Zc:forScope', # handle 'for (int i = 0 ...)' right
+ '/EHs-c-', # disable C++ EH
+ '/GR-', # disable RTTI
+ '/wd4996', # ignore POSIX deprecated warnings
+
+ # promote certain level 4 warnings
+ '/w14701', # potentially uninitialized var
+ '/w14702', # unreachable code
+ '/w14706', # assignment within a conditional
+ '/w14709', # comma operator within array index
+ '/w14063', # case 'identifier' is not a valid value for switch of enum
+ '/w14064', # switch of incomplete enum 'enumeration'
+ '/w14057', # 'identifier1' indirection to slightly different base
+ # types from 'identifier2'
+ '/w14263', # member function does not override any base class virtual
+ # member function
+ '/w14266', # no override available for virtual memberfunction from base
+ # 'type'; function is hidden
+ '/w14296', # expression is always false
+ '/w14355', # 'this' : used in base member initializer list
+ ],
+ CPPDEFINES = [
+ '_ATL_CSTRING_EXPLICIT_CONSTRUCTORS',
+ # TODO: encapsulate all string operations that are not based
+ # on std::string/std::wstring and make sure we use the safest versions
+ # available on all platforms.
+ '_CRT_SECURE_NO_WARNINGS',
+ '_SCL_SECURE_NO_WARNINGS',
+ '_USE_32BIT_TIME_T',
+ '_UNICODE',
+ 'UNICODE',
+ '_HAS_EXCEPTIONS=0',
+ 'WIN32',
+ # TODO: remove this from logging.cc and enable here instead.
+ #'WIN32_LEAN_AND_MEAN',
+
+ 'WINVER=0x0500',
+ '_WIN32_WINNT=0x0501',
+ '_WIN32_IE=0x0501',
+ # The Vista platform SDK 6.0 needs at least
+ # this NTDDI version or else the headers
+ # that LMI includes from it won't compile.
+ 'NTDDI_VERSION=NTDDI_WINXP',
+
+ # npapi.h requires the following:
+ '_WINDOWS',
+ ],
+ CPPPATH = [
+ '$THIRD_PARTY/wtl_71/include',
+ '$PLATFORM_SDK_VISTA_6_0_DIR/Include',
+ ],
+ LIBPATH = [
+ '$PLATFORM_SDK_VISTA_6_0_DIR/Lib'
+ ],
+ LINKFLAGS = [
+ '-manifest' # TODO: Why do we need this?
+ ],
+ MIDLFLAGS = [
+ '/win32',
+ '/I$PLATFORM_SDK_VISTA_6_0_DIR/include'
+ ]
+)
+
+# TODO: Figure out what this does; found it in
+# omaha/main.scons. This fixes the problem with redefinition
+# of OS_WINDOWS symbol.
+win_env.FilterOut(CPPDEFINES = ['OS_WINDOWS=OS_WINDOWS'])
+
+# Set up digital signing
+DeclareBit('test_signing', 'Sign binaries with the test certificate')
+win_env.SetBitFromOption('test_signing', False)
+if win_env.Bit('test_signing'):
+ win_env.Replace(
+ CERTIFICATE_PATH = win_env.File(
+ '$GOOGLECLIENT/tools/test_key/testkey.pfx').abspath,
+ CERTIFICATE_PASSWORD = 'test',
+ )
+AddTargetGroup('signed_binaries', 'digitally signed binaries can be built')
+
+win_dbg_env = win_env.Clone(
+ BUILD_TYPE = 'dbg',
+ BUILD_TYPE_DESCRIPTION = 'Windows debug build',
+ BUILD_GROUPS = ['default', 'all'],
+ tools = ['target_debug'],
+)
+
+win_dbg_env.Prepend(
+ CCFLAGS = [
+ '/ZI', # enable debugging
+ '/Od', # disable optimizations
+ '/MTd', # link with LIBCMTD.LIB debug lib
+ '/RTC1', # enable runtime checks
+ ],
+)
+
+envs.append(win_dbg_env)
+
+win_coverage_env = win_dbg_env.Clone(
+ tools = ['code_coverage'],
+ BUILD_TYPE = 'coverage',
+ BUILD_TYPE_DESCRIPTION = 'Windows code coverage build',
+ BUILD_GROUPS = ['all'],
+)
+
+win_coverage_env.Append(
+ CPPDEFINES = [
+ 'COVERAGE_ENABLED',
+ ],
+)
+
+envs.append(win_coverage_env)
+
+win_opt_env = win_env.Clone(
+ BUILD_TYPE = 'opt',
+ BUILD_TYPE_DESCRIPTION = 'Windows opt build',
+ BUILD_GROUPS = ['all'],
+ tools = ['target_optimized'],
+)
+
+win_opt_env.Prepend(
+ CCFLAGS=[
+ '/Zi', # enable debugging
+ '/O1', # optimize for size
+ '/MT', # link with LIBCMT.LIB (multi-threaded, static linked crt)
+ '/GS', # enable security checks
+ ],
+ LINKFLAGS = [
+ '/safeseh',
+ ],
+)
+
+envs.append(win_opt_env)
+
+
+#-------------------------------------------------------------------------------
+# P O S I X
+#
+posix_env = root_env.Clone()
+posix_env.Append(
+ CPPDEFINES = [
+ 'HASHNAMESPACE=__gnu_cxx',
+ 'HASH_NAMESPACE=__gnu_cxx',
+ 'POSIX',
+ 'DISABLE_DYNAMIC_CAST',
+ 'HAVE_OPENSSL_SSL_H=1',
+ # The POSIX standard says we have to define this.
+ '_REENTRANT',
+ ],
+ CCFLAGS = [
+ '-Wall',
+ '-Werror',
+ '-Wno-switch',
+ '-fno-exceptions',
+ ],
+ CXXFLAGS = [
+ '-Wno-non-virtual-dtor',
+ '-Wno-ctor-dtor-privacy',
+ '-fno-rtti',
+ ],
+)
+
+#-------------------------------------------------------------------------------
+# M A C OSX
+#
+mac_env = posix_env.Clone(
+ tools = [
+ 'target_platform_mac',
+ #'talk_mac',
+ #'fill_plist',
+ ],
+)
+mac_env.Append(
+ CPPDEFINES = [
+ 'OSX',
+ 'MAC_OS_X_VERSION_MIN_REQUIRED=1040',
+ ],
+ CCFLAGS = [
+ '-m32',
+ '-arch', 'i386',
+ '-isysroot', '/Developer/SDKs/MacOSX10.5.sdk',
+ '-fasm-blocks',
+ ],
+ LINKFLAGS = [
+ '-Wl,-search_paths_first',
+ # This flag makes all members of a static library be included in the
+ # final exe - that increases the size of the exe, but without it
+ # Obj-C categories aren't properly included in the exe.
+ # TODO: consider only defining for libs that actually have objc.
+ '-ObjC',
+ '-arch', 'i386',
+ '-m32',
+ ],
+ FRAMEWORKS = [
+ 'CoreServices',
+ 'Carbon',
+ 'Security',
+ 'SystemConfiguration',
+ 'OpenGL',
+ 'CoreAudio',
+ 'Quartz',
+ 'QuickTime',
+ 'Cocoa',
+ 'QTKit',
+ ]
+)
+
+mac_dbg_env = mac_env.Clone(
+ BUILD_TYPE = 'dbg',
+ BUILD_TYPE_DESCRIPTION = 'Mac debug build',
+ BUILD_GROUPS = ['default', 'all'],
+ tools = ['target_debug'],
+)
+mac_dbg_env.Append(
+ CCFLAGS = [
+ '-O0',
+ ]
+)
+envs.append(mac_dbg_env)
+
+mac_opt_env = mac_env.Clone(
+ BUILD_TYPE = 'opt',
+ BUILD_TYPE_DESCRIPTION = 'Mac opt build',
+ BUILD_GROUPS = ['all'],
+ tools = ['target_optimized'],
+)
+mac_opt_env.Append(
+ CCFLAGS = [
+ # TODO: Figure out how mk can compile without
+ # this flag, then remove. Confirmed asserts are preprocessed
+ # out. Maybe it's a different version of gcc?
+ '-Wno-unused-variable',
+ ],
+)
+envs.append(mac_opt_env)
+
+#-------------------------------------------------------------------------------
+# L I N U X
+#
+linux_common_env = posix_env.Clone(
+ tools = [
+ 'target_platform_linux',
+ #'talk_linux',
+ ],
+)
+
+linux_common_env.Append(
+ CPPDEFINES = [
+ 'LINUX',
+ 'HAVE_GLIB',
+# 'HAVE_DBUS_GLIB',
+ ],
+ CCFLAGS = [
+ # TODO: Some or all of this may be desirable for Mac too.
+ # Needed for link-time dead-code removal to work properly.
+ '-ffunction-sections',
+ '-fdata-sections',
+ # Needed for a clean ABI and for link-time dead-code removal to work
+ # properly.
+ '-fvisibility=hidden',
+ # Generate debugging info in the DWARF2 format.
+ '-gdwarf-2',
+ # Generate maximal debugging information. (It is stripped from what we ship
+ # to users, so we want it for both dbg and opt.)
+ '-g3',
+ ],
+ LINKFLAGS = [
+ # Enable dead-code removal.
+ '-Wl,--gc-sections',
+ '-Wl,--start-group',
+ ],
+ _LIBFLAGS = ['-Wl,--end-group'],
+)
+
+# Remove default rpath set by Hammer. Hammer sets it to LIB_DIR, which is wrong.
+# The rpath is the _run-time_ library search path for the resulting binary, i.e.
+# the one used by ld.so at load time. Setting it equal to the path to build
+# output on the build machine is nonsense.
+linux_common_env.Replace(
+ RPATH = [],
+)
+
+#-------------------------------------------------------------------------------
+# L I N U X -- T R A D I T I O N A L
+#
+# Settings that are specific to our desktop Linux targets.
+linux_env = linux_common_env.Clone()
+# OpenSSL has infamously poor ABI stability, so that building against one
+# version and running against a different one often will not work. Since our
+# non-ChromeOS Linux builds are used on many different distros and distro
+# versions, this means we can't safely dynamically link to OpenSSL because the
+# product would end up being broken on any computer with a different version
+# installed. So instead we build it ourself and statically link to it.
+linux_env.SetBits('use_static_openssl')
+linux_env.Append(
+ CCFLAGS = [
+ '-m32',
+ ],
+ LINKFLAGS = [
+ '-m32',
+ ],
+)
+
+linux_dbg_env = linux_env.Clone(
+ BUILD_TYPE = 'dbg',
+ BUILD_TYPE_DESCRIPTION = 'Linux debug build',
+ BUILD_GROUPS = ['default', 'all'],
+ tools = ['target_debug'],
+)
+# Remove -g set by hammer, which is not what we want (we have set -g3 above).
+linux_dbg_env.FilterOut(CCFLAGS = ['-g'])
+envs.append(linux_dbg_env)
+
+linux_opt_env = linux_env.Clone(
+ BUILD_TYPE = 'opt',
+ BUILD_TYPE_DESCRIPTION = 'Linux optimized build',
+ BUILD_GROUPS = ['all'],
+ tools = ['target_optimized'],
+)
+# Remove -O2 set by hammer, which is not what we want.
+linux_opt_env.FilterOut(CCFLAGS = ['-O2'])
+linux_opt_env.Append(CCFLAGS = ['-Os'])
+envs.append(linux_opt_env)
+
+
+
+# TODO(): Clone linux envs for 64bit. See 'variant' documentation.
+
+# Create a group for installers
+AddTargetGroup('all_installers', 'installers that can be built')
+
+# Parse child .scons files
+BuildEnvironments(envs)
+
+# Explicitly set which targets to build when not stated on commandline
+Default(None)
+# Build the following, which excludes unit test output (ie running them)
+# To run unittests, specify the test to run, or run_all_tests. See -h option.
+Default(['all_libraries', 'all_programs', 'all_test_programs'])
+
+# .sln creation code lifted from googleclient/bar/main.scons. Must be after
+# the call to BuildEnvironments for all_foo aliases to be defined.
+# Run 'hammer --mode=all --vsproj' to generate
+DeclareBit('vsproj', 'Generate Visual Studio projects and solution files.')
+win_env.SetBitFromOption('vsproj', False)
+
+if win_env.Bit('vsproj'):
+ vs_env = win_env.Clone()
+ vs_env.Append(
+ COMPONENT_VS_SOURCE_SUFFIXES = [
+ '.def',
+ '.grd',
+ '.html',
+ '.idl',
+ '.mk',
+ '.txt',
+ '.py',
+ '.scons',
+ '.wxs.template',
+ ]
+ )
+
+ # Source project
+ p = vs_env.ComponentVSDirProject(
+ 'flute_source',
+ ['$MAIN_DIR',
+ '$THIRD_PARTY'],
+ COMPONENT_VS_SOURCE_FOLDERS = [
+ # Files are assigned to first matching folder. Folder names of None
+ # are filters.
+ (None, '$DESTINATION_ROOT'),
+ ('flute', '$MAIN_DIR'),
+ ('google3', '$GOOGLE3'),
+ ('third_party', '$THIRD_PARTY'),
+ ],
+ # Force source project to main dir, so that Visual Studio can find the
+ # source files corresponding to build errors.
+ COMPONENT_VS_PROJECT_DIR = '$MAIN_DIR',
+ )
+ vs_env.AlwaysBuild(p)
+
+ # Solution and target projects
+ s = vs_env.ComponentVSSolution(
+ # 'libjingle', # Please uncomment this line if you build VS proj files.
+ ['all_libraries', 'all_programs', 'all_test_programs'],
+ projects = [p],
+ )
+
+ print '***Unfortunately the vsproj creator isn\'t smart enough to '
+ print '***automatically get the correct output locations. It is very easy'
+ print '***though to change it in the properties pane to the following'
+ print '***($SolutionDir)/build/<foo>/staging/<bar>.exe'
+ Default(None)
+ Default([s])
diff --git a/talk/p2p/base/candidate.h b/talk/p2p/base/candidate.h
new file mode 100644
index 0000000..e347ec5
--- /dev/null
+++ b/talk/p2p/base/candidate.h
@@ -0,0 +1,137 @@
+/*
+ * 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_P2P_BASE_CANDIDATE_H_
+#define TALK_P2P_BASE_CANDIDATE_H_
+
+#include <string>
+#include <sstream>
+#include "talk/base/socketaddress.h"
+
+namespace cricket {
+
+// Candidate for ICE based connection discovery.
+
+class Candidate {
+ public:
+ Candidate() : preference_(0), generation_(0) {}
+ Candidate(const std::string& name, const std::string& protocol,
+ const talk_base::SocketAddress& address, float preference,
+ const std::string& username, const std::string& password,
+ const std::string& type, const std::string& network_name,
+ uint32 generation)
+ : name_(name), protocol_(protocol), address_(address),
+ preference_(preference), username_(username), password_(password),
+ type_(type), network_name_(network_name), generation_(generation) {}
+
+ const std::string & name() const { return name_; }
+ void set_name(const std::string & name) { name_ = name; }
+
+ const std::string & protocol() const { return protocol_; }
+ void set_protocol(const std::string & protocol) { protocol_ = protocol; }
+
+ const talk_base::SocketAddress & address() const { return address_; }
+ void set_address(const talk_base::SocketAddress & address) {
+ address_ = address;
+ }
+
+ float preference() const { return preference_; }
+ void set_preference(const float preference) { preference_ = preference; }
+ const std::string preference_str() const {
+ std::ostringstream ost;
+ ost << preference_;
+ return ost.str();
+ }
+ void set_preference_str(const std::string & preference) {
+ std::istringstream ist(preference);
+ ist >> preference_;
+ }
+
+ const std::string & username() const { return username_; }
+ void set_username(const std::string & username) { username_ = username; }
+
+ const std::string & password() const { return password_; }
+ void set_password(const std::string & password) { password_ = password; }
+
+ const std::string & type() const { return type_; }
+ void set_type(const std::string & type) { type_ = type; }
+
+ const std::string & network_name() const { return network_name_; }
+ void set_network_name(const std::string & network_name) {
+ network_name_ = network_name;
+ }
+
+ // Candidates in a new generation replace those in the old generation.
+ uint32 generation() const { return generation_; }
+ void set_generation(uint32 generation) { generation_ = generation; }
+ const std::string generation_str() const {
+ std::ostringstream ost;
+ ost << generation_;
+ return ost.str();
+ }
+ void set_generation_str(const std::string& str) {
+ std::istringstream ist(str);
+ ist >> generation_;
+ }
+
+ // Determines whether this candidate is equivalent to the given one.
+ bool IsEquivalent(const Candidate& c) const {
+ // We ignore the network name, since that is just debug information, and
+ // the preference, since that should be the same if the rest is (and it's
+ // a float so equality checking is always worrisome).
+ return (name_ == c.name_) &&
+ (protocol_ == c.protocol_) &&
+ (address_ == c.address_) &&
+ (username_ == c.username_) &&
+ (password_ == c.password_) &&
+ (type_ == c.type_) &&
+ (generation_ == c.generation_);
+ }
+
+ std::string ToString() const {
+ std::ostringstream ost;
+ ost << "Cand[" << name_ << ":" << type_ << ":" << protocol_ << ":"
+ << network_name_ << ":" << address_.ToString() << ":"
+ << username_ << ":" << password_ << "]";
+ return ost.str();
+ }
+
+ private:
+ std::string name_;
+ std::string protocol_;
+ talk_base::SocketAddress address_;
+ float preference_;
+ std::string username_;
+ std::string password_;
+ std::string type_;
+ std::string network_name_;
+ uint32 generation_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_CANDIDATE_H_
diff --git a/talk/p2p/base/common.h b/talk/p2p/base/common.h
new file mode 100644
index 0000000..5a38180
--- /dev/null
+++ b/talk/p2p/base/common.h
@@ -0,0 +1,37 @@
+/*
+ * 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_P2P_BASE_COMMON_H_
+#define TALK_P2P_BASE_COMMON_H_
+
+#include "talk/base/logging.h"
+
+// Common log description format for jingle messages
+#define LOG_J(sev, obj) LOG(sev) << "Jingle:" << obj->ToString() << ": "
+#define LOG_JV(sev, obj) LOG_V(sev) << "Jingle:" << obj->ToString() << ": "
+
+#endif // TALK_P2P_BASE_COMMON_H_
diff --git a/talk/p2p/base/constants.cc b/talk/p2p/base/constants.cc
new file mode 100644
index 0000000..f44ce21
--- /dev/null
+++ b/talk/p2p/base/constants.cc
@@ -0,0 +1,229 @@
+/*
+ * 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 <string>
+
+#include "talk/p2p/base/constants.h"
+#include "talk/xmllite/qname.h"
+
+namespace cricket {
+
+const std::string NS_EMPTY("");
+const std::string NS_JINGLE("urn:xmpp:jingle:1");
+const std::string NS_GINGLE("http://www.google.com/session");
+
+// actions (aka <session> or <jingle>)
+const buzz::QName QN_ACTION(true, NS_EMPTY, "action");
+const std::string LN_INITIATOR("initiator");
+const buzz::QName QN_INITIATOR(true, NS_EMPTY, LN_INITIATOR);
+const buzz::QName QN_CREATOR(true, NS_EMPTY, "creator");
+
+const buzz::QName QN_JINGLE(true, NS_JINGLE, "jingle");
+const buzz::QName QN_JINGLE_CONTENT(true, NS_JINGLE, "content");
+const buzz::QName QN_JINGLE_CONTENT_NAME(true, NS_EMPTY, "name");
+const buzz::QName QN_JINGLE_CONTENT_MEDIA(true, NS_EMPTY, "media");
+const buzz::QName QN_JINGLE_REASON(true, NS_JINGLE, "reason");
+const std::string JINGLE_CONTENT_MEDIA_AUDIO("audio");
+const std::string JINGLE_CONTENT_MEDIA_VIDEO("video");
+const std::string JINGLE_ACTION_SESSION_INITIATE("session-initiate");
+const std::string JINGLE_ACTION_SESSION_INFO("session-info");
+const std::string JINGLE_ACTION_SESSION_ACCEPT("session-accept");
+const std::string JINGLE_ACTION_SESSION_TERMINATE("session-terminate");
+const std::string JINGLE_ACTION_TRANSPORT_INFO("transport-info");
+const std::string JINGLE_ACTION_TRANSPORT_ACCEPT("transport-accept");
+
+const buzz::QName QN_GINGLE_SESSION(true, NS_GINGLE, "session");
+const std::string GINGLE_ACTION_INITIATE("initiate");
+const std::string GINGLE_ACTION_INFO("info");
+const std::string GINGLE_ACTION_ACCEPT("accept");
+const std::string GINGLE_ACTION_REJECT("reject");
+const std::string GINGLE_ACTION_TERMINATE("terminate");
+const std::string GINGLE_ACTION_CANDIDATES("candidates");
+const std::string GINGLE_ACTION_NOTIFY("notify");
+const std::string GINGLE_ACTION_UPDATE("update");
+const std::string GINGLE_ACTION_VIEW("view");
+
+const std::string LN_ERROR("error");
+const buzz::QName QN_GINGLE_REDIRECT(true, NS_GINGLE, "redirect");
+const std::string STR_REDIRECT_PREFIX("xmpp:");
+
+// Session Contents (aka Gingle <session><description>
+// or Jingle <content><description>)
+const std::string LN_DESCRIPTION("description");
+const std::string LN_PAYLOADTYPE("payload-type");
+const buzz::QName QN_ID(true, NS_EMPTY, "id");
+const buzz::QName QN_SID(true, NS_EMPTY, "sid");
+const buzz::QName QN_NAME(true, NS_EMPTY, "name");
+const buzz::QName QN_CLOCKRATE(true, NS_EMPTY, "clockrate");
+const buzz::QName QN_BITRATE(true, NS_EMPTY, "bitrate");
+const buzz::QName QN_CHANNELS(true, NS_EMPTY, "channels");
+const buzz::QName QN_WIDTH(true, NS_EMPTY, "width");
+const buzz::QName QN_HEIGHT(true, NS_EMPTY, "height");
+const buzz::QName QN_FRAMERATE(true, NS_EMPTY, "framerate");
+const std::string LN_NAME("name");
+const std::string LN_VALUE("value");
+const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME(true, NS_EMPTY, LN_NAME);
+const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE(true, NS_EMPTY, LN_VALUE);
+const std::string PAYLOADTYPE_PARAMETER_BITRATE("bitrate");
+const std::string PAYLOADTYPE_PARAMETER_HEIGHT("height");
+const std::string PAYLOADTYPE_PARAMETER_WIDTH("width");
+const std::string PAYLOADTYPE_PARAMETER_FRAMERATE("framerate");
+const std::string LN_BANDWIDTH("bandwidth");
+
+const std::string CN_AUDIO("audio");
+const std::string CN_VIDEO("video");
+const std::string CN_OTHER("main");
+
+const std::string NS_JINGLE_RTP("urn:xmpp:jingle:apps:rtp:1");
+const buzz::QName QN_JINGLE_RTP_CONTENT(
+ true, NS_JINGLE_RTP, LN_DESCRIPTION);
+const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE(
+ true, NS_JINGLE_RTP, LN_PAYLOADTYPE);
+const buzz::QName QN_JINGLE_RTP_BANDWIDTH(
+ true, NS_JINGLE_RTP, LN_BANDWIDTH);
+const buzz::QName QN_PARAMETER(true, NS_JINGLE_RTP, "parameter");
+
+const std::string NS_GINGLE_AUDIO("http://www.google.com/session/phone");
+const buzz::QName QN_GINGLE_AUDIO_CONTENT(
+ true, NS_GINGLE_AUDIO, LN_DESCRIPTION);
+const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE(
+ true, NS_GINGLE_AUDIO, LN_PAYLOADTYPE);
+const buzz::QName QN_GINGLE_AUDIO_SRCID(true, NS_GINGLE_AUDIO, "src-id");
+const std::string NS_GINGLE_VIDEO("http://www.google.com/session/video");
+const buzz::QName QN_GINGLE_VIDEO_CONTENT(
+ true, NS_GINGLE_VIDEO, LN_DESCRIPTION);
+const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE(
+ true, NS_GINGLE_VIDEO, LN_PAYLOADTYPE);
+const buzz::QName QN_GINGLE_VIDEO_SRCID(true, NS_GINGLE_VIDEO, "src-id");
+const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH(
+ true, NS_GINGLE_VIDEO, LN_BANDWIDTH);
+
+// Crypto support.
+const buzz::QName QN_ENCRYPTION(true, NS_JINGLE_RTP, "encryption");
+const buzz::QName QN_ENCRYPTION_REQUIRED(true, NS_EMPTY, "required");
+const buzz::QName QN_CRYPTO(true, NS_JINGLE_RTP, "crypto");
+const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE(true, NS_GINGLE_AUDIO, "usage");
+const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE(true, NS_GINGLE_VIDEO, "usage");
+const buzz::QName QN_CRYPTO_SUITE(true, NS_EMPTY, "crypto-suite");
+const buzz::QName QN_CRYPTO_KEY_PARAMS(true, NS_EMPTY, "key-params");
+const buzz::QName QN_CRYPTO_TAG(true, NS_EMPTY, "tag");
+const buzz::QName QN_CRYPTO_SESSION_PARAMS(true, NS_EMPTY, "session-params");
+
+// transports and candidates
+const std::string LN_TRANSPORT("transport");
+const std::string LN_CANDIDATE("candidate");
+const buzz::QName QN_UFRAG(true, cricket::NS_EMPTY, "ufrag");
+const buzz::QName QN_PWD(true, cricket::NS_EMPTY, "pwd");
+const buzz::QName QN_COMPONENT(true, cricket::NS_EMPTY, "component");
+const buzz::QName QN_IP(true, cricket::NS_EMPTY, "ip");
+const buzz::QName QN_PORT(true, cricket::NS_EMPTY, "port");
+const buzz::QName QN_NETWORK(true, cricket::NS_EMPTY, "network");
+const buzz::QName QN_GENERATION(true, cricket::NS_EMPTY, "generation");
+const buzz::QName QN_PRIORITY(true, cricket::NS_EMPTY, "priority");
+const buzz::QName QN_PROTOCOL(true, cricket::NS_EMPTY, "protocol");
+const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN("prflx");
+const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN("srflx");
+const std::string JINGLE_CANDIDATE_NAME_RTP("1");
+const std::string JINGLE_CANDIDATE_NAME_RTCP("2");
+
+// TODO Once we are full ICE-UDP compliant, use this namespace.
+// For now, just use the same as NS_GINGLE_P2P.
+// const std::string NS_JINGLE_ICE_UDP("urn:xmpp:jingle:transports:ice-udp:1");
+const std::string NS_GINGLE_P2P("http://www.google.com/transport/p2p");
+const buzz::QName QN_GINGLE_P2P_TRANSPORT(true, NS_GINGLE_P2P, LN_TRANSPORT);
+const buzz::QName QN_GINGLE_P2P_CANDIDATE(true, NS_GINGLE_P2P, LN_CANDIDATE);
+const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME(
+ true, NS_GINGLE_P2P, "unknown-channel-name");
+const buzz::QName QN_GINGLE_CANDIDATE(true, NS_GINGLE, LN_CANDIDATE);
+const buzz::QName QN_ADDRESS(true, cricket::NS_EMPTY, "address");
+const buzz::QName QN_USERNAME(true, cricket::NS_EMPTY, "username");
+const buzz::QName QN_PASSWORD(true, cricket::NS_EMPTY, "password");
+const buzz::QName QN_PREFERENCE(true, cricket::NS_EMPTY, "preference");
+const std::string GINGLE_CANDIDATE_TYPE_STUN("stun");
+const std::string GINGLE_CANDIDATE_NAME_RTP("rtp");
+const std::string GINGLE_CANDIDATE_NAME_RTCP("rtcp");
+const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP("video_rtp");
+const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP("video_rtcp");
+
+// terminate reasons and errors
+const std::string JINGLE_ERROR_BAD_REQUEST("bad-request");
+const std::string JINGLE_ERROR_OUT_OF_ORDER("out-of-order");
+const std::string JINGLE_ERROR_UNKNOWN_SESSION("unknown-session");
+
+// Call terminate reasons from XEP-166
+const std::string STR_TERMINATE_DECLINE("decline");
+const std::string STR_TERMINATE_SUCCESS("success");
+const std::string STR_TERMINATE_ERROR("general-error");
+const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS(
+ "incompatible-parameters");
+
+// Old terminate reasons used by cricket
+const std::string STR_TERMINATE_CALL_ENDED("call-ended");
+const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE("recipient-unavailable");
+const std::string STR_TERMINATE_RECIPIENT_BUSY("recipient-busy");
+const std::string STR_TERMINATE_INSUFFICIENT_FUNDS("insufficient-funds");
+const std::string STR_TERMINATE_NUMBER_MALFORMED("number-malformed");
+const std::string STR_TERMINATE_NUMBER_DISALLOWED("number-disallowed");
+const std::string STR_TERMINATE_PROTOCOL_ERROR("protocol-error");
+const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR("internal-server-error");
+const std::string STR_TERMINATE_UNKNOWN_ERROR("unknown-error");
+
+// Session notify messages
+const buzz::QName QN_GINGLE_NOTIFY(true, NS_GINGLE, "notify");
+const buzz::QName QN_GINGLE_NOTIFY_NICK(true, cricket::NS_EMPTY, "nick");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE(true, NS_GINGLE, "source");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE_MTYPE(
+ true, cricket::NS_EMPTY, "mtype");
+const buzz::QName QN_GINGLE_NOTIFY_SOURCE_SSRC(true, cricket::NS_EMPTY, "ssrc");
+const std::string GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO("audio");
+const std::string GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO("video");
+
+// Session view messages
+const buzz::QName QN_GINGLE_VIEW(true, cricket::NS_EMPTY, "view");
+const buzz::QName QN_GINGLE_VIEW_TYPE(true, cricket::NS_EMPTY, "type");
+const buzz::QName QN_GINGLE_VIEW_NICK(true, cricket::NS_EMPTY, "nick");
+const buzz::QName QN_GINGLE_VIEW_MEDIA_TYPE(true, cricket::NS_EMPTY, "mtype");
+const buzz::QName QN_GINGLE_VIEW_SSRC(true, cricket::NS_EMPTY, "ssrc");
+const std::string GINGLE_VIEW_TYPE_STATIC("static");
+const std::string GINGLE_VIEW_TYPE_DYNAMIC("dynamic");
+const std::string GINGLE_VIEW_MEDIA_TYPE_AUDIO("audio");
+const std::string GINGLE_VIEW_MEDIA_TYPE_VIDEO("video");
+const buzz::QName QN_GINGLE_VIEW_PARAMS(true, cricket::NS_EMPTY, "params");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_WIDTH(true, cricket::NS_EMPTY, "width");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_HEIGHT(
+ true, cricket::NS_EMPTY, "height");
+const buzz::QName QN_GINGLE_VIEW_PARAMS_FRAMERATE(
+ true, cricket::NS_EMPTY, "framerate");
+
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const std::string NS_VOICEMAIL("http://www.google.com/session/voicemail");
+const buzz::QName QN_VOICEMAIL_REGARDING(true, NS_VOICEMAIL, "regarding");
+#endif
+
+} // namespace cricket
diff --git a/talk/p2p/base/constants.h b/talk/p2p/base/constants.h
new file mode 100644
index 0000000..cd2e2c5
--- /dev/null
+++ b/talk/p2p/base/constants.h
@@ -0,0 +1,242 @@
+/*
+ * 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_P2P_BASE_CONSTANTS_H_
+#define TALK_P2P_BASE_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+
+// This file contains constants related to signaling that are used in various
+// classes in this directory.
+
+namespace cricket {
+
+// NS_ == namespace
+// QN_ == buzz::QName (namespace + name)
+// LN_ == "local name" == QName::LocalPart()
+// these are useful when you need to find a tag
+// that has different namespaces (like <description> or <transport>)
+
+extern const std::string NS_EMPTY;
+extern const std::string NS_JINGLE;
+extern const std::string NS_GINGLE;
+
+enum SignalingProtocol {
+ PROTOCOL_JINGLE,
+ PROTOCOL_GINGLE,
+ PROTOCOL_HYBRID,
+};
+
+// actions (aka Gingle <session> or Jingle <jingle>)
+extern const buzz::QName QN_ACTION;
+extern const std::string LN_INITIATOR;
+extern const buzz::QName QN_INITIATOR;
+extern const buzz::QName QN_CREATOR;
+
+extern const buzz::QName QN_JINGLE;
+extern const buzz::QName QN_JINGLE_CONTENT;
+extern const buzz::QName QN_JINGLE_CONTENT_NAME;
+extern const buzz::QName QN_JINGLE_CONTENT_MEDIA;
+extern const buzz::QName QN_JINGLE_REASON;
+extern const std::string JINGLE_CONTENT_MEDIA_AUDIO;
+extern const std::string JINGLE_CONTENT_MEDIA_VIDEO;
+extern const std::string JINGLE_ACTION_SESSION_INITIATE;
+extern const std::string JINGLE_ACTION_SESSION_INFO;
+extern const std::string JINGLE_ACTION_SESSION_ACCEPT;
+extern const std::string JINGLE_ACTION_SESSION_TERMINATE;
+extern const std::string JINGLE_ACTION_TRANSPORT_INFO;
+extern const std::string JINGLE_ACTION_TRANSPORT_ACCEPT;
+
+extern const buzz::QName QN_GINGLE_SESSION;
+extern const std::string GINGLE_ACTION_INITIATE;
+extern const std::string GINGLE_ACTION_INFO;
+extern const std::string GINGLE_ACTION_ACCEPT;
+extern const std::string GINGLE_ACTION_REJECT;
+extern const std::string GINGLE_ACTION_TERMINATE;
+extern const std::string GINGLE_ACTION_CANDIDATES;
+extern const std::string GINGLE_ACTION_NOTIFY;
+extern const std::string GINGLE_ACTION_UPDATE;
+extern const std::string GINGLE_ACTION_VIEW;
+
+extern const std::string LN_ERROR;
+extern const buzz::QName QN_GINGLE_REDIRECT;
+extern const std::string STR_REDIRECT_PREFIX;
+
+// Session Contents (aka Gingle <session><description>
+// or Jingle <content><description>)
+extern const std::string LN_DESCRIPTION;
+extern const std::string LN_PAYLOADTYPE;
+extern const buzz::QName QN_ID;
+extern const buzz::QName QN_SID;
+extern const buzz::QName QN_NAME;
+extern const buzz::QName QN_CLOCKRATE;
+extern const buzz::QName QN_BITRATE;
+extern const buzz::QName QN_CHANNELS;
+extern const buzz::QName QN_WIDTH;
+extern const buzz::QName QN_HEIGHT;
+extern const buzz::QName QN_FRAMERATE;
+extern const buzz::QName QN_PARAMETER;
+extern const std::string LN_NAME;
+extern const std::string LN_VALUE;
+extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_NAME;
+extern const buzz::QName QN_PAYLOADTYPE_PARAMETER_VALUE;
+extern const std::string PAYLOADTYPE_PARAMETER_BITRATE;
+extern const std::string PAYLOADTYPE_PARAMETER_HEIGHT;
+extern const std::string PAYLOADTYPE_PARAMETER_WIDTH;
+extern const std::string PAYLOADTYPE_PARAMETER_FRAMERATE;
+extern const std::string LN_BANDWIDTH;
+
+// CN_ == "content name". When we initiate a session, we choose the
+// name, and when we receive a Gingle session, we provide default
+// names (since Gingle has no content names). But when we receive a
+// Jingle call, the content name can be anything, so don't rely on
+// these values being the same as the ones received.
+extern const std::string CN_AUDIO;
+extern const std::string CN_VIDEO;
+extern const std::string CN_OTHER;
+
+extern const std::string NS_JINGLE_RTP;
+extern const buzz::QName QN_JINGLE_RTP_CONTENT;
+extern const buzz::QName QN_JINGLE_RTP_PAYLOADTYPE;
+extern const buzz::QName QN_JINGLE_RTP_BANDWIDTH;
+
+extern const std::string NS_GINGLE_AUDIO;
+extern const buzz::QName QN_GINGLE_AUDIO_CONTENT;
+extern const buzz::QName QN_GINGLE_AUDIO_PAYLOADTYPE;
+extern const buzz::QName QN_GINGLE_AUDIO_SRCID;
+extern const std::string NS_GINGLE_VIDEO;
+extern const buzz::QName QN_GINGLE_VIDEO_CONTENT;
+extern const buzz::QName QN_GINGLE_VIDEO_PAYLOADTYPE;
+extern const buzz::QName QN_GINGLE_VIDEO_SRCID;
+extern const buzz::QName QN_GINGLE_VIDEO_BANDWIDTH;
+
+// Crypto support.
+extern const buzz::QName QN_ENCRYPTION;
+extern const buzz::QName QN_ENCRYPTION_REQUIRED;
+extern const buzz::QName QN_CRYPTO;
+extern const buzz::QName QN_GINGLE_AUDIO_CRYPTO_USAGE;
+extern const buzz::QName QN_GINGLE_VIDEO_CRYPTO_USAGE;
+extern const buzz::QName QN_CRYPTO_SUITE;
+extern const buzz::QName QN_CRYPTO_KEY_PARAMS;
+extern const buzz::QName QN_CRYPTO_TAG;
+extern const buzz::QName QN_CRYPTO_SESSION_PARAMS;
+
+// transports and candidates
+extern const std::string LN_TRANSPORT;
+extern const std::string LN_CANDIDATE;
+extern const buzz::QName QN_JINGLE_P2P_TRANSPORT;
+extern const buzz::QName QN_JINGLE_P2P_CANDIDATE;
+extern const buzz::QName QN_UFRAG;
+extern const buzz::QName QN_COMPONENT;
+extern const buzz::QName QN_PWD;
+extern const buzz::QName QN_IP;
+extern const buzz::QName QN_PORT;
+extern const buzz::QName QN_NETWORK;
+extern const buzz::QName QN_GENERATION;
+extern const buzz::QName QN_PRIORITY;
+extern const buzz::QName QN_PROTOCOL;
+extern const std::string JINGLE_CANDIDATE_TYPE_PEER_STUN;
+extern const std::string JINGLE_CANDIDATE_TYPE_SERVER_STUN;
+extern const std::string JINGLE_CANDIDATE_NAME_RTP;
+extern const std::string JINGLE_CANDIDATE_NAME_RTCP;
+
+extern const std::string NS_GINGLE_P2P;
+extern const buzz::QName QN_GINGLE_P2P_TRANSPORT;
+extern const buzz::QName QN_GINGLE_P2P_CANDIDATE;
+extern const buzz::QName QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME;
+extern const buzz::QName QN_GINGLE_CANDIDATE;
+extern const buzz::QName QN_ADDRESS;
+extern const buzz::QName QN_USERNAME;
+extern const buzz::QName QN_PASSWORD;
+extern const buzz::QName QN_PREFERENCE;
+extern const std::string GINGLE_CANDIDATE_TYPE_STUN;
+extern const std::string GINGLE_CANDIDATE_NAME_RTP;
+extern const std::string GINGLE_CANDIDATE_NAME_RTCP;
+extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTP;
+extern const std::string GINGLE_CANDIDATE_NAME_VIDEO_RTCP;
+
+extern const std::string NS_GINGLE_RAW;
+extern const buzz::QName QN_GINGLE_RAW_TRANSPORT;
+extern const buzz::QName QN_GINGLE_RAW_CHANNEL;
+
+// terminate reasons and errors: see http://xmpp.org/extensions/xep-0166.html
+extern const std::string JINGLE_ERROR_BAD_REQUEST; // like parse error
+// got transport-info before session-initiate, for example
+extern const std::string JINGLE_ERROR_OUT_OF_ORDER;
+extern const std::string JINGLE_ERROR_UNKNOWN_SESSION;
+
+// Call terminate reasons from XEP-166
+extern const std::string STR_TERMINATE_DECLINE; // polite reject
+extern const std::string STR_TERMINATE_SUCCESS; // polite hangup
+extern const std::string STR_TERMINATE_ERROR; // something bad happened
+extern const std::string STR_TERMINATE_INCOMPATIBLE_PARAMETERS; // no codecs?
+
+// Old terminate reasons used by cricket
+extern const std::string STR_TERMINATE_CALL_ENDED;
+extern const std::string STR_TERMINATE_RECIPIENT_UNAVAILABLE;
+extern const std::string STR_TERMINATE_RECIPIENT_BUSY;
+extern const std::string STR_TERMINATE_INSUFFICIENT_FUNDS;
+extern const std::string STR_TERMINATE_NUMBER_MALFORMED;
+extern const std::string STR_TERMINATE_NUMBER_DISALLOWED;
+extern const std::string STR_TERMINATE_PROTOCOL_ERROR;
+extern const std::string STR_TERMINATE_INTERNAL_SERVER_ERROR;
+extern const std::string STR_TERMINATE_UNKNOWN_ERROR;
+
+// Session notify messages
+extern const buzz::QName QN_GINGLE_NOTIFY;
+extern const buzz::QName QN_GINGLE_NOTIFY_NICK;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE_MTYPE;
+extern const buzz::QName QN_GINGLE_NOTIFY_SOURCE_SSRC;
+extern const std::string GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO;
+extern const std::string GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO;
+
+// Session view messages
+extern const buzz::QName QN_GINGLE_VIEW;
+extern const buzz::QName QN_GINGLE_VIEW_TYPE;
+extern const buzz::QName QN_GINGLE_VIEW_NICK;
+extern const buzz::QName QN_GINGLE_VIEW_MEDIA_TYPE;
+extern const buzz::QName QN_GINGLE_VIEW_SSRC;
+extern const std::string GINGLE_VIEW_TYPE_STATIC;
+extern const std::string GINGLE_VIEW_TYPE_DYNAMIC;
+extern const std::string GINGLE_VIEW_MEDIA_TYPE_AUDIO;
+extern const std::string GINGLE_VIEW_MEDIA_TYPE_VIDEO;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_WIDTH;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_HEIGHT;
+extern const buzz::QName QN_GINGLE_VIEW_PARAMS_FRAMERATE;
+
+// old stuff
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const std::string NS_VOICEMAIL;
+extern const buzz::QName QN_VOICEMAIL_REGARDING;
+#endif
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_CONSTANTS_H_
diff --git a/talk/p2p/base/p2ptransport.cc b/talk/p2p/base/p2ptransport.cc
new file mode 100644
index 0000000..dd170ff
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.cc
@@ -0,0 +1,201 @@
+/*
+ * 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 "talk/p2p/base/p2ptransport.h"
+
+#include <string>
+#include <vector>
+
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/base/stringencode.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace {
+
+// We only allow usernames to be this many characters or fewer.
+const size_t kMaxUsernameSize = 16;
+
+} // namespace
+
+namespace cricket {
+
+P2PTransport::P2PTransport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ PortAllocator* allocator)
+ : Transport(signaling_thread, worker_thread,
+ NS_GINGLE_P2P, allocator) {
+}
+
+P2PTransport::~P2PTransport() {
+ DestroyAllChannels();
+}
+
+void P2PTransport::OnTransportError(const buzz::XmlElement* error) {
+ // Need to know if it was <unknown-channel name="xxx">.
+ ASSERT(error->Name().Namespace() == type());
+ if ((error->Name() == QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME)
+ && error->HasAttr(buzz::QN_NAME)) {
+ std::string channel_name = error->Attr(buzz::QN_NAME);
+ if (HasChannel(channel_name)) {
+ SignalChannelGone(this, channel_name);
+ }
+ }
+}
+
+
+bool P2PTransportParser::ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ Candidates* candidates,
+ ParseError* error) {
+ // TODO: Once we implement standard ICE-UDP, parse the
+ // candidates according to XEP-176.
+ for (const buzz::XmlElement* candidate_elem = elem->FirstElement();
+ candidate_elem != NULL;
+ candidate_elem = candidate_elem->NextElement()) {
+ // Only look at local part because it might be <session><candidate>
+ // or <tranport><candidate>.
+ if (candidate_elem->Name().LocalPart() == LN_CANDIDATE) {
+ Candidate candidate;
+ if (!ParseCandidate(candidate_elem, &candidate, error))
+ return false;
+ candidates->push_back(candidate);
+ }
+ }
+ return true;
+}
+
+bool P2PTransportParser::ParseCandidate(const buzz::XmlElement* elem,
+ Candidate* candidate,
+ ParseError* error) {
+ if (!elem->HasAttr(buzz::QN_NAME) ||
+ !elem->HasAttr(QN_ADDRESS) ||
+ !elem->HasAttr(QN_PORT) ||
+ !elem->HasAttr(QN_USERNAME) ||
+ !elem->HasAttr(QN_PREFERENCE) ||
+ !elem->HasAttr(QN_PROTOCOL) ||
+ !elem->HasAttr(QN_GENERATION)) {
+ return BadParse("candidate missing required attribute", error);
+ }
+
+ talk_base::SocketAddress address;
+ if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, &address, error))
+ return false;
+
+ candidate->set_name(elem->Attr(buzz::QN_NAME));
+ candidate->set_address(address);
+ candidate->set_username(elem->Attr(QN_USERNAME));
+ candidate->set_preference_str(elem->Attr(QN_PREFERENCE));
+ candidate->set_protocol(elem->Attr(QN_PROTOCOL));
+ candidate->set_generation_str(elem->Attr(QN_GENERATION));
+ if (elem->HasAttr(QN_PASSWORD))
+ candidate->set_password(elem->Attr(QN_PASSWORD));
+ if (elem->HasAttr(buzz::QN_TYPE))
+ candidate->set_type(elem->Attr(buzz::QN_TYPE));
+ if (elem->HasAttr(QN_NETWORK))
+ candidate->set_network_name(elem->Attr(QN_NETWORK));
+
+ if (!VerifyUsernameFormat(candidate->username(), error))
+ return false;
+
+ return true;
+}
+
+bool P2PTransportParser::VerifyUsernameFormat(const std::string& username,
+ ParseError* error) {
+ if (username.size() > kMaxUsernameSize)
+ return BadParse("candidate username is too long", error);
+ if (!talk_base::Base64::IsBase64Encoded(username))
+ return BadParse(
+ "candidate username has non-base64 encoded characters", error);
+ return true;
+}
+
+const buzz::QName& GetCandidateQName(SignalingProtocol protocol) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return QN_GINGLE_CANDIDATE;
+ } else {
+ // TODO: Once we implement standard ICE-UDP, use the
+ // XEP-176 namespace.
+ return QN_GINGLE_P2P_CANDIDATE;
+ }
+}
+
+bool P2PTransportParser::WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ XmlElements* candidate_elems,
+ WriteError* error) {
+ // TODO: Once we implement standard ICE-UDP, parse the
+ // candidates according to XEP-176.
+ for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+ iter != candidates.end(); ++iter) {
+ buzz::XmlElement* cand_elem =
+ new buzz::XmlElement(GetCandidateQName(protocol));
+ if (!WriteCandidate(*iter, cand_elem, error))
+ return false;
+ candidate_elems->push_back(cand_elem);
+ }
+ return true;
+}
+
+bool P2PTransportParser::WriteCandidate(const Candidate& candidate,
+ buzz::XmlElement* elem,
+ WriteError* error) {
+ elem->SetAttr(buzz::QN_NAME, candidate.name());
+ elem->SetAttr(QN_ADDRESS, candidate.address().IPAsString());
+ elem->SetAttr(QN_PORT, candidate.address().PortAsString());
+ elem->SetAttr(QN_PREFERENCE, candidate.preference_str());
+ elem->SetAttr(QN_USERNAME, candidate.username());
+ elem->SetAttr(QN_PROTOCOL, candidate.protocol());
+ elem->SetAttr(QN_GENERATION, candidate.generation_str());
+ if (candidate.password().size() > 0)
+ elem->SetAttr(QN_PASSWORD, candidate.password());
+ if (candidate.type().size() > 0)
+ elem->SetAttr(buzz::QN_TYPE, candidate.type());
+ if (candidate.network_name().size() > 0)
+ elem->SetAttr(QN_NETWORK, candidate.network_name());
+ return true;
+}
+
+TransportChannelImpl* P2PTransport::CreateTransportChannel(
+ const std::string& name, const std::string& content_type) {
+ return new P2PTransportChannel(name, content_type, this, port_allocator());
+}
+
+void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+ delete channel;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/p2ptransport.h b/talk/p2p/base/p2ptransport.h
new file mode 100644
index 0000000..084f487
--- /dev/null
+++ b/talk/p2p/base/p2ptransport.h
@@ -0,0 +1,83 @@
+/*
+ * 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_P2P_BASE_P2PTRANSPORT_H_
+#define TALK_P2P_BASE_P2PTRANSPORT_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/transport.h"
+
+namespace cricket {
+
+class P2PTransport: public Transport {
+ public:
+ P2PTransport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ PortAllocator* allocator);
+ virtual ~P2PTransport();
+
+ virtual void OnTransportError(const buzz::XmlElement* error);
+
+ protected:
+ // Creates and destroys P2PTransportChannel.
+ virtual TransportChannelImpl* CreateTransportChannel(
+ const std::string& name, const std::string& content_type);
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ friend class P2PTransportChannel;
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransport);
+};
+
+class P2PTransportParser : public TransportParser {
+ public:
+ P2PTransportParser() {}
+ virtual bool ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ Candidates* candidates,
+ ParseError* error);
+ virtual bool WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ XmlElements* candidate_elems,
+ WriteError* error);
+ private:
+ bool ParseCandidate(const buzz::XmlElement* elem,
+ Candidate* candidate,
+ ParseError* error);
+ bool WriteCandidate(const Candidate& candidate,
+ buzz::XmlElement* elem,
+ WriteError* error);
+ bool VerifyUsernameFormat(const std::string& username,
+ ParseError* error);
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransportParser);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_P2PTRANSPORT_H_
diff --git a/talk/p2p/base/p2ptransportchannel.cc b/talk/p2p/base/p2ptransportchannel.cc
new file mode 100644
index 0000000..1b2bec2
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.cc
@@ -0,0 +1,926 @@
+/*
+ * 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 "talk/p2p/base/p2ptransportchannel.h"
+
+#include <set>
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace {
+
+// messages for queuing up work for ourselves
+const uint32 MSG_SORT = 1;
+const uint32 MSG_PING = 2;
+const uint32 MSG_ALLOCATE = 3;
+
+// When the socket is unwritable, we will use 10 Kbps (ignoring IP+UDP headers)
+// for pinging. When the socket is writable, we will use only 1 Kbps because
+// we don't want to degrade the quality on a modem. These numbers should work
+// well on a 28.8K modem, which is the slowest connection on which the voice
+// quality is reasonable at all.
+static const uint32 PING_PACKET_SIZE = 60 * 8;
+static const uint32 WRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 1000; // 480ms
+static const uint32 UNWRITABLE_DELAY = 1000 * PING_PACKET_SIZE / 10000; // 50ms
+
+// If there is a current writable connection, then we will also try hard to
+// make sure it is pinged at this rate.
+static const uint32 MAX_CURRENT_WRITABLE_DELAY = 900; // 2*WRITABLE_DELAY - bit
+
+// The minimum improvement in RTT that justifies a switch.
+static const double kMinImprovement = 10;
+
+// Amount of time that we wait when *losing* writability before we try doing
+// another allocation.
+static const int kAllocateDelay = 1 * 1000; // 1 second
+
+// We will try creating a new allocator from scratch after a delay of this
+// length without becoming writable (or timing out).
+static const int kAllocatePeriod = 20 * 1000; // 20 seconds
+
+cricket::Port::CandidateOrigin GetOrigin(cricket::Port* port,
+ cricket::Port* origin_port) {
+ if (!origin_port)
+ return cricket::Port::ORIGIN_MESSAGE;
+ else if (port == origin_port)
+ return cricket::Port::ORIGIN_THIS_PORT;
+ else
+ return cricket::Port::ORIGIN_OTHER_PORT;
+}
+
+// Compares two connections based only on static information about them.
+int CompareConnectionCandidates(cricket::Connection* a,
+ cricket::Connection* b) {
+ // Combine local and remote preferences
+ ASSERT(a->local_candidate().preference() == a->port()->preference());
+ ASSERT(b->local_candidate().preference() == b->port()->preference());
+ double a_pref = a->local_candidate().preference()
+ * a->remote_candidate().preference();
+ double b_pref = b->local_candidate().preference()
+ * b->remote_candidate().preference();
+
+ // Now check combined preferences. Lower values get sorted last.
+ if (a_pref > b_pref)
+ return 1;
+ if (a_pref < b_pref)
+ return -1;
+
+ return 0;
+}
+
+// Compare two connections based on their writability and static preferences.
+int CompareConnections(cricket::Connection *a, cricket::Connection *b) {
+ // Sort based on write-state. Better states have lower values.
+ if (a->write_state() < b->write_state())
+ return 1;
+ if (a->write_state() > b->write_state())
+ return -1;
+
+ // Compare the candidate information.
+ return CompareConnectionCandidates(a, b);
+}
+
+// Wraps the comparison connection into a less than operator that puts higher
+// priority writable connections first.
+class ConnectionCompare {
+ public:
+ bool operator()(const cricket::Connection *ca,
+ const cricket::Connection *cb) {
+ cricket::Connection* a = const_cast<cricket::Connection*>(ca);
+ cricket::Connection* b = const_cast<cricket::Connection*>(cb);
+
+ // Compare first on writability and static preferences.
+ int cmp = CompareConnections(a, b);
+ if (cmp > 0)
+ return true;
+ if (cmp < 0)
+ return false;
+
+ // Otherwise, sort based on latency estimate.
+ return a->rtt() < b->rtt();
+
+ // Should we bother checking for the last connection that last received
+ // data? It would help rendezvous on the connection that is also receiving
+ // packets.
+ //
+ // TODO: Yes we should definitely do this. The TCP protocol gains
+ // efficiency by being used bidirectionally, as opposed to two separate
+ // unidirectional streams. This test should probably occur before
+ // comparison of local prefs (assuming combined prefs are the same). We
+ // need to be careful though, not to bounce back and forth with both sides
+ // trying to rendevous with the other.
+ }
+};
+
+// Determines whether we should switch between two connections, based first on
+// static preferences and then (if those are equal) on latency estimates.
+bool ShouldSwitch(cricket::Connection* a_conn, cricket::Connection* b_conn) {
+ if (a_conn == b_conn)
+ return false;
+
+ if (!a_conn || !b_conn) // don't think the latter should happen
+ return true;
+
+ int prefs_cmp = CompareConnections(a_conn, b_conn);
+ if (prefs_cmp < 0)
+ return true;
+ if (prefs_cmp > 0)
+ return false;
+
+ return b_conn->rtt() <= a_conn->rtt() + kMinImprovement;
+}
+
+} // unnamed namespace
+
+namespace cricket {
+
+P2PTransportChannel::P2PTransportChannel(const std::string &name,
+ const std::string &content_type,
+ P2PTransport* transport,
+ PortAllocator *allocator) :
+ TransportChannelImpl(name, content_type),
+ transport_(transport),
+ allocator_(allocator),
+ worker_thread_(talk_base::Thread::Current()),
+ waiting_for_signaling_(false),
+ error_(0),
+ best_connection_(NULL),
+ pinging_started_(false),
+ sort_dirty_(false),
+ was_writable_(false),
+ was_timed_out_(true) {
+}
+
+P2PTransportChannel::~P2PTransportChannel() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+ delete allocator_sessions_[i];
+}
+
+// Add the allocator session to our list so that we know which sessions
+// are still active.
+void P2PTransportChannel::AddAllocatorSession(PortAllocatorSession* session) {
+ session->set_generation(static_cast<uint32>(allocator_sessions_.size()));
+ allocator_sessions_.push_back(session);
+
+ // We now only want to apply new candidates that we receive to the ports
+ // created by this new session because these are replacing those of the
+ // previous sessions.
+ ports_.clear();
+
+ session->SignalPortReady.connect(this, &P2PTransportChannel::OnPortReady);
+ session->SignalCandidatesReady.connect(
+ this, &P2PTransportChannel::OnCandidatesReady);
+ session->GetInitialPorts();
+ if (pinging_started_)
+ session->StartGetAllPorts();
+}
+
+// Go into the state of processing candidates, and running in general
+void P2PTransportChannel::Connect() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Kick off an allocator session
+ Allocate();
+
+ // Start pinging as the ports come in.
+ thread()->Post(this, MSG_PING);
+}
+
+// Reset the socket, clear up any previous allocations and start over
+void P2PTransportChannel::Reset() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Get rid of all the old allocators. This should clean up everything.
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i)
+ delete allocator_sessions_[i];
+
+ allocator_sessions_.clear();
+ ports_.clear();
+ connections_.clear();
+ best_connection_ = NULL;
+
+ // Forget about all of the candidates we got before.
+ remote_candidates_.clear();
+
+ // Revert to the initial state.
+ set_readable(false);
+ set_writable(false);
+
+ // Reinitialize the rest of our state.
+ waiting_for_signaling_ = false;
+ pinging_started_ = false;
+ sort_dirty_ = false;
+ was_writable_ = false;
+ was_timed_out_ = true;
+
+ // If we allocated before, start a new one now.
+ if (transport_->connect_requested())
+ Allocate();
+
+ // Start pinging as the ports come in.
+ thread()->Clear(this);
+ thread()->Post(this, MSG_PING);
+}
+
+// A new port is available, attempt to make connections for it
+void P2PTransportChannel::OnPortReady(PortAllocatorSession *session,
+ Port* port) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Set in-effect options on the new port
+ for (OptionMap::const_iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ int val = port->SetOption(it->first, it->second);
+ if (val < 0) {
+ LOG_J(LS_WARNING, port) << "SetOption(" << it->first
+ << ", " << it->second
+ << ") failed: " << port->GetError();
+ }
+ }
+
+ // Remember the ports and candidates, and signal that candidates are ready.
+ // The session will handle this, and send an initiate/accept/modify message
+ // if one is pending.
+
+ ports_.push_back(port);
+ port->SignalUnknownAddress.connect(
+ this, &P2PTransportChannel::OnUnknownAddress);
+ port->SignalDestroyed.connect(this, &P2PTransportChannel::OnPortDestroyed);
+
+ // Attempt to create a connection from this new port to all of the remote
+ // candidates that we were given so far.
+
+ std::vector<RemoteCandidate>::iterator iter;
+ for (iter = remote_candidates_.begin(); iter != remote_candidates_.end();
+ ++iter)
+ CreateConnection(port, *iter, iter->origin_port(), false);
+
+ SortConnections();
+}
+
+// A new candidate is available, let listeners know
+void P2PTransportChannel::OnCandidatesReady(
+ PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+ for (size_t i = 0; i < candidates.size(); ++i) {
+ SignalCandidateReady(this, candidates[i]);
+ }
+}
+
+// Handle stun packets
+void P2PTransportChannel::OnUnknownAddress(
+ Port *port, const talk_base::SocketAddress &address, StunMessage *stun_msg,
+ const std::string &remote_username) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Port has received a valid stun packet from an address that no Connection
+ // is currently available for. See if the remote user name is in the remote
+ // candidate list. If it isn't return error to the stun request.
+
+ const Candidate *candidate = NULL;
+ std::vector<RemoteCandidate>::iterator it;
+ for (it = remote_candidates_.begin(); it != remote_candidates_.end(); ++it) {
+ if ((*it).username() == remote_username) {
+ candidate = &(*it);
+ break;
+ }
+ }
+ if (candidate == NULL) {
+ // Don't know about this username, the request is bogus
+ // This sometimes happens if a binding response comes in before the ACCEPT
+ // message. It is totally valid; the retry state machine will try again.
+
+ port->SendBindingErrorResponse(stun_msg, address,
+ STUN_ERROR_STALE_CREDENTIALS, STUN_ERROR_REASON_STALE_CREDENTIALS);
+ delete stun_msg;
+ return;
+ }
+
+ // Check for connectivity to this address. Create connections
+ // to this address across all local ports. First, add this as a new remote
+ // address
+
+ Candidate new_remote_candidate = *candidate;
+ new_remote_candidate.set_address(address);
+ // new_remote_candidate.set_protocol(port->protocol());
+
+ // This remote username exists. Now create connections using this candidate,
+ // and resort
+
+ if (CreateConnections(new_remote_candidate, port, true)) {
+ // Send the pinger a successful stun response.
+ port->SendBindingResponse(stun_msg, address);
+
+ // Update the list of connections since we just added another. We do this
+ // after sending the response since it could (in principle) delete the
+ // connection in question.
+ SortConnections();
+ } else {
+ // Hopefully this won't occur, because changing a destination address
+ // shouldn't cause a new connection to fail
+ ASSERT(false);
+ port->SendBindingErrorResponse(stun_msg, address, STUN_ERROR_SERVER_ERROR,
+ STUN_ERROR_REASON_SERVER_ERROR);
+ }
+
+ delete stun_msg;
+}
+
+void P2PTransportChannel::OnCandidate(const Candidate& candidate) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Create connections to this remote candidate.
+ CreateConnections(candidate, NULL, false);
+
+ // Resort the connections list, which may have new elements.
+ SortConnections();
+}
+
+// Creates connections from all of the ports that we care about to the given
+// remote candidate. The return value is true if we created a connection from
+// the origin port.
+bool P2PTransportChannel::CreateConnections(const Candidate &remote_candidate,
+ Port* origin_port,
+ bool readable) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Add a new connection for this candidate to every port that allows such a
+ // connection (i.e., if they have compatible protocols) and that does not
+ // already have a connection to an equivalent candidate. We must be careful
+ // to make sure that the origin port is included, even if it was pruned,
+ // since that may be the only port that can create this connection.
+
+ bool created = false;
+
+ std::vector<Port *>::reverse_iterator it;
+ for (it = ports_.rbegin(); it != ports_.rend(); ++it) {
+ if (CreateConnection(*it, remote_candidate, origin_port, readable)) {
+ if (*it == origin_port)
+ created = true;
+ }
+ }
+
+ if ((origin_port != NULL) &&
+ std::find(ports_.begin(), ports_.end(), origin_port) == ports_.end()) {
+ if (CreateConnection(origin_port, remote_candidate, origin_port, readable))
+ created = true;
+ }
+
+ // Remember this remote candidate so that we can add it to future ports.
+ RememberRemoteCandidate(remote_candidate, origin_port);
+
+ return created;
+}
+
+// Setup a connection object for the local and remote candidate combination.
+// And then listen to connection object for changes.
+bool P2PTransportChannel::CreateConnection(Port* port,
+ const Candidate& remote_candidate,
+ Port* origin_port,
+ bool readable) {
+ // Look for an existing connection with this remote address. If one is not
+ // found, then we can create a new connection for this address.
+ Connection* connection = port->GetConnection(remote_candidate.address());
+ if (connection != NULL) {
+ // It is not legal to try to change any of the parameters of an existing
+ // connection; however, the other side can send a duplicate candidate.
+ if (!remote_candidate.IsEquivalent(connection->remote_candidate())) {
+ LOG(INFO) << "Attempt to change a remote candidate";
+ return false;
+ }
+ } else {
+ Port::CandidateOrigin origin = GetOrigin(port, origin_port);
+ connection = port->CreateConnection(remote_candidate, origin);
+ if (!connection)
+ return false;
+
+ connections_.push_back(connection);
+ connection->SignalReadPacket.connect(
+ this, &P2PTransportChannel::OnReadPacket);
+ connection->SignalStateChange.connect(
+ this, &P2PTransportChannel::OnConnectionStateChange);
+ connection->SignalDestroyed.connect(
+ this, &P2PTransportChannel::OnConnectionDestroyed);
+
+ LOG_J(LS_INFO, this) << "Created connection with origin=" << origin << ", ("
+ << connections_.size() << " total)";
+ }
+
+ // If we are readable, it is because we are creating this in response to a
+ // ping from the other side. This will cause the state to become readable.
+ if (readable)
+ connection->ReceivedPing();
+
+ return true;
+}
+
+// Maintain our remote candidate list, adding this new remote one.
+void P2PTransportChannel::RememberRemoteCandidate(
+ const Candidate& remote_candidate, Port* origin_port) {
+ // Remove any candidates whose generation is older than this one. The
+ // presence of a new generation indicates that the old ones are not useful.
+ uint32 i = 0;
+ while (i < remote_candidates_.size()) {
+ if (remote_candidates_[i].generation() < remote_candidate.generation()) {
+ LOG(INFO) << "Pruning candidate from old generation: "
+ << remote_candidates_[i].address().ToString();
+ remote_candidates_.erase(remote_candidates_.begin() + i);
+ } else {
+ i += 1;
+ }
+ }
+
+ // Make sure this candidate is not a duplicate.
+ for (uint32 i = 0; i < remote_candidates_.size(); ++i) {
+ if (remote_candidates_[i].IsEquivalent(remote_candidate)) {
+ LOG(INFO) << "Duplicate candidate: "
+ << remote_candidate.address().ToString();
+ return;
+ }
+ }
+
+ // Try this candidate for all future ports.
+ remote_candidates_.push_back(RemoteCandidate(remote_candidate, origin_port));
+
+ // We have some candidates from the other side, we are now serious about
+ // this connection. Let's do the StartGetAllPorts thing.
+ if (!pinging_started_) {
+ pinging_started_ = true;
+ for (size_t i = 0; i < allocator_sessions_.size(); ++i) {
+ if (!allocator_sessions_[i]->IsGettingAllPorts())
+ allocator_sessions_[i]->StartGetAllPorts();
+ }
+ }
+}
+
+// Send data to the other side, using our best connection
+int P2PTransportChannel::SendPacket(const char *data, size_t len) {
+ // This can get called on any thread that is convenient to write from!
+ if (best_connection_ == NULL) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ int sent = best_connection_->Send(data, len);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = best_connection_->GetError();
+ }
+ return sent;
+}
+
+// Begin allocate (or immediately re-allocate, if MSG_ALLOCATE pending)
+void P2PTransportChannel::Allocate() {
+ CancelPendingAllocate();
+ // Time for a new allocator, lets make sure we have a signalling channel
+ // to communicate candidates through first.
+ waiting_for_signaling_ = true;
+ SignalRequestSignaling();
+}
+
+// Cancels the pending allocate, if any.
+void P2PTransportChannel::CancelPendingAllocate() {
+ thread()->Clear(this, MSG_ALLOCATE);
+}
+
+// Monitor connection states
+void P2PTransportChannel::UpdateConnectionStates() {
+ uint32 now = talk_base::Time();
+
+ // We need to copy the list of connections since some may delete themselves
+ // when we call UpdateState.
+ for (uint32 i = 0; i < connections_.size(); ++i)
+ connections_[i]->UpdateState(now);
+}
+
+// Prepare for best candidate sorting
+void P2PTransportChannel::RequestSort() {
+ if (!sort_dirty_) {
+ worker_thread_->Post(this, MSG_SORT);
+ sort_dirty_ = true;
+ }
+}
+
+// Sort the available connections to find the best one. We also monitor
+// the number of available connections and the current state so that we
+// can possibly kick off more allocators (for more connections).
+void P2PTransportChannel::SortConnections() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Make sure the connection states are up-to-date since this affects how they
+ // will be sorted.
+ UpdateConnectionStates();
+
+ // Any changes after this point will require a re-sort.
+ sort_dirty_ = false;
+
+ // Get a list of the networks that we are using.
+ std::set<talk_base::Network*> networks;
+ for (uint32 i = 0; i < connections_.size(); ++i)
+ networks.insert(connections_[i]->port()->network());
+
+ // Find the best alternative connection by sorting. It is important to note
+ // that amongst equal preference, writable connections, this will choose the
+ // one whose estimated latency is lowest. So it is the only one that we
+ // need to consider switching to.
+
+ ConnectionCompare cmp;
+ std::stable_sort(connections_.begin(), connections_.end(), cmp);
+ Connection* top_connection = NULL;
+ if (connections_.size() > 0)
+ top_connection = connections_[0];
+
+ // If necessary, switch to the new choice.
+ if (ShouldSwitch(best_connection_, top_connection))
+ SwitchBestConnectionTo(top_connection);
+
+ // We can prune any connection for which there is a writable connection on
+ // the same network with better or equal prefences. We leave those with
+ // better preference just in case they become writable later (at which point,
+ // we would prune out the current best connection). We leave connections on
+ // other networks because they may not be using the same resources and they
+ // may represent very distinct paths over which we can switch.
+ std::set<talk_base::Network*>::iterator network;
+ for (network = networks.begin(); network != networks.end(); ++network) {
+ Connection* primier = GetBestConnectionOnNetwork(*network);
+ if (!primier || (primier->write_state() != Connection::STATE_WRITABLE))
+ continue;
+
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if ((connections_[i] != primier) &&
+ (connections_[i]->port()->network() == *network) &&
+ (CompareConnectionCandidates(primier, connections_[i]) >= 0)) {
+ connections_[i]->Prune();
+ }
+ }
+ }
+
+ // Count the number of connections in the various states.
+
+ int writable = 0;
+ int write_connect = 0;
+ int write_timeout = 0;
+
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ switch (connections_[i]->write_state()) {
+ case Connection::STATE_WRITABLE:
+ ++writable;
+ break;
+ case Connection::STATE_WRITE_CONNECT:
+ ++write_connect;
+ break;
+ case Connection::STATE_WRITE_TIMEOUT:
+ ++write_timeout;
+ break;
+ default:
+ ASSERT(false);
+ }
+ }
+
+ if (writable > 0) {
+ HandleWritable();
+ } else if (write_connect > 0) {
+ HandleNotWritable();
+ } else {
+ HandleAllTimedOut();
+ }
+
+ // Update the state of this channel. This method is called whenever the
+ // state of any connection changes, so this is a good place to do this.
+ UpdateChannelState();
+
+ // Notify of connection state change
+ SignalConnectionMonitor(this);
+}
+
+// Track the best connection, and let listeners know
+void P2PTransportChannel::SwitchBestConnectionTo(Connection* conn) {
+ // Note: if conn is NULL, the previous best_connection_ has been destroyed,
+ // so don't use it.
+ // use it.
+ Connection* old_best_connection = best_connection_;
+ best_connection_ = conn;
+ if (best_connection_) {
+ if (old_best_connection) {
+ LOG_J(LS_INFO, this) << "Previous best connection: "
+ << old_best_connection->ToString();
+ }
+ LOG_J(LS_INFO, this) << "New best connection: "
+ << best_connection_->ToString();
+ SignalRouteChange(this, best_connection_->remote_candidate().address());
+ } else {
+ LOG_J(LS_INFO, this) << "No best connection";
+ }
+}
+
+void P2PTransportChannel::UpdateChannelState() {
+ // The Handle* functions already set the writable state. We'll just double-
+ // check it here.
+ bool writable = ((best_connection_ != NULL) &&
+ (best_connection_->write_state() ==
+ Connection::STATE_WRITABLE));
+ ASSERT(writable == this->writable());
+ if (writable != this->writable())
+ LOG(LS_ERROR) << "UpdateChannelState: writable state mismatch";
+
+ bool readable = false;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (connections_[i]->read_state() == Connection::STATE_READABLE)
+ readable = true;
+ }
+ set_readable(readable);
+}
+
+// We checked the status of our connections and we had at least one that
+// was writable, go into the writable state.
+void P2PTransportChannel::HandleWritable() {
+ //
+ // One or more connections writable!
+ //
+ if (!writable()) {
+ for (uint32 i = 0; i < allocator_sessions_.size(); ++i) {
+ if (allocator_sessions_[i]->IsGettingAllPorts()) {
+ allocator_sessions_[i]->StopGetAllPorts();
+ }
+ }
+
+ // Stop further allocations.
+ CancelPendingAllocate();
+ }
+
+ // We're writable, obviously we aren't timed out
+ was_writable_ = true;
+ was_timed_out_ = false;
+ set_writable(true);
+}
+
+// We checked the status of our connections and we didn't have any that
+// were writable, go into the connecting state (kick off a new allocator
+// session).
+void P2PTransportChannel::HandleNotWritable() {
+ //
+ // No connections are writable but not timed out!
+ //
+ if (was_writable_) {
+ // If we were writable, let's kick off an allocator session immediately
+ was_writable_ = false;
+ Allocate();
+ }
+
+ // We were connecting, obviously not ALL timed out.
+ was_timed_out_ = false;
+ set_writable(false);
+}
+
+// We checked the status of our connections and not only weren't they writable
+// but they were also timed out, we really need a new allocator.
+void P2PTransportChannel::HandleAllTimedOut() {
+ //
+ // No connections... all are timed out!
+ //
+ if (!was_timed_out_) {
+ // We weren't timed out before, so kick off an allocator now (we'll still
+ // be in the fully timed out state until the allocator actually gives back
+ // new ports)
+ Allocate();
+ }
+
+ // NOTE: we start was_timed_out_ in the true state so that we don't get
+ // another allocator created WHILE we are in the process of building up
+ // our first allocator.
+ was_timed_out_ = true;
+ was_writable_ = false;
+ set_writable(false);
+}
+
+// If we have a best connection, return it, otherwise return top one in the
+// list (later we will mark it best).
+Connection* P2PTransportChannel::GetBestConnectionOnNetwork(
+ talk_base::Network* network) {
+ // If the best connection is on this network, then it wins.
+ if (best_connection_ && (best_connection_->port()->network() == network))
+ return best_connection_;
+
+ // Otherwise, we return the top-most in sorted order.
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (connections_[i]->port()->network() == network)
+ return connections_[i];
+ }
+
+ return NULL;
+}
+
+// Handle any queued up requests
+void P2PTransportChannel::OnMessage(talk_base::Message *pmsg) {
+ if (pmsg->message_id == MSG_SORT)
+ OnSort();
+ else if (pmsg->message_id == MSG_PING)
+ OnPing();
+ else if (pmsg->message_id == MSG_ALLOCATE)
+ Allocate();
+ else
+ ASSERT(false);
+}
+
+// Handle queued up sort request
+void P2PTransportChannel::OnSort() {
+ // Resort the connections based on the new statistics.
+ SortConnections();
+}
+
+// Handle queued up ping request
+void P2PTransportChannel::OnPing() {
+ // Make sure the states of the connections are up-to-date (since this affects
+ // which ones are pingable).
+ UpdateConnectionStates();
+
+ // Find the oldest pingable connection and have it do a ping.
+ Connection* conn = FindNextPingableConnection();
+ if (conn)
+ conn->Ping(talk_base::Time());
+
+ // Post ourselves a message to perform the next ping.
+ uint32 delay = writable() ? WRITABLE_DELAY : UNWRITABLE_DELAY;
+ thread()->PostDelayed(delay, this, MSG_PING);
+}
+
+// Is the connection in a state for us to even consider pinging the other side?
+bool P2PTransportChannel::IsPingable(Connection* conn) {
+ // An unconnected connection cannot be written to at all, so pinging is out
+ // of the question.
+ if (!conn->connected())
+ return false;
+
+ if (writable()) {
+ // If we are writable, then we only want to ping connections that could be
+ // better than this one, i.e., the ones that were not pruned.
+ return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT);
+ } else {
+ // If we are not writable, then we need to try everything that might work.
+ // This includes both connections that do not have write timeout as well as
+ // ones that do not have read timeout. A connection could be readable but
+ // be in write-timeout if we pruned it before. Since the other side is
+ // still pinging it, it very well might still work.
+ return (conn->write_state() != Connection::STATE_WRITE_TIMEOUT) ||
+ (conn->read_state() != Connection::STATE_READ_TIMEOUT);
+ }
+}
+
+// Returns the next pingable connection to ping. This will be the oldest
+// pingable connection unless we have a writable connection that is past the
+// maximum acceptable ping delay.
+Connection* P2PTransportChannel::FindNextPingableConnection() {
+ uint32 now = talk_base::Time();
+ if (best_connection_ &&
+ (best_connection_->write_state() == Connection::STATE_WRITABLE) &&
+ (best_connection_->last_ping_sent()
+ + MAX_CURRENT_WRITABLE_DELAY <= now)) {
+ return best_connection_;
+ }
+
+ Connection* oldest_conn = NULL;
+ uint32 oldest_time = 0xFFFFFFFF;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (IsPingable(connections_[i])) {
+ if (connections_[i]->last_ping_sent() < oldest_time) {
+ oldest_time = connections_[i]->last_ping_sent();
+ oldest_conn = connections_[i];
+ }
+ }
+ }
+ return oldest_conn;
+}
+
+// return the number of "pingable" connections
+uint32 P2PTransportChannel::NumPingableConnections() {
+ uint32 count = 0;
+ for (uint32 i = 0; i < connections_.size(); ++i) {
+ if (IsPingable(connections_[i]))
+ count += 1;
+ }
+ return count;
+}
+
+// When a connection's state changes, we need to figure out who to use as
+// the best connection again. It could have become usable, or become unusable.
+void P2PTransportChannel::OnConnectionStateChange(Connection *connection) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // We have to unroll the stack before doing this because we may be changing
+ // the state of connections while sorting.
+ RequestSort();
+}
+
+// When a connection is removed, edit it out, and then update our best
+// connection.
+void P2PTransportChannel::OnConnectionDestroyed(Connection *connection) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Note: the previous best_connection_ may be destroyed by now, so don't
+ // use it.
+
+ // Remove this connection from the list.
+ std::vector<Connection*>::iterator iter =
+ std::find(connections_.begin(), connections_.end(), connection);
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+
+ LOG_J(LS_INFO, this) << "Removed connection ("
+ << static_cast<int>(connections_.size()) << " remaining)";
+
+ // If this is currently the best connection, then we need to pick a new one.
+ // The call to SortConnections will pick a new one. It looks at the current
+ // best connection in order to avoid switching between fairly similar ones.
+ // Since this connection is no longer an option, we can just set best to NULL
+ // and re-choose a best assuming that there was no best connection.
+ if (best_connection_ == connection) {
+ SwitchBestConnectionTo(NULL);
+ RequestSort();
+ }
+}
+
+// When a port is destroyed remove it from our list of ports to use for
+// connection attempts.
+void P2PTransportChannel::OnPortDestroyed(Port* port) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Remove this port from the list (if we didn't drop it already).
+ std::vector<Port*>::iterator iter =
+ std::find(ports_.begin(), ports_.end(), port);
+ if (iter != ports_.end())
+ ports_.erase(iter);
+
+ LOG(INFO) << "Removed port from p2p socket: "
+ << static_cast<int>(ports_.size()) << " remaining";
+}
+
+// We data is available, let listeners know
+void P2PTransportChannel::OnReadPacket(Connection *connection,
+ const char *data, size_t len) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ // Let the client know of an incoming packet
+
+ SignalReadPacket(this, data, len);
+}
+
+// Set options on ourselves is simply setting options on all of our available
+// port objects.
+int P2PTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+ OptionMap::iterator it = options_.find(opt);
+ if (it == options_.end()) {
+ options_.insert(std::make_pair(opt, value));
+ } else if (it->second == value) {
+ return 0;
+ } else {
+ it->second = value;
+ }
+
+ for (uint32 i = 0; i < ports_.size(); ++i) {
+ int val = ports_[i]->SetOption(opt, value);
+ if (val < 0) {
+ // Because this also occurs deferred, probably no point in reporting an
+ // error
+ LOG(WARNING) << "SetOption(" << opt << ", " << value << ") failed: "
+ << ports_[i]->GetError();
+ }
+ }
+ return 0;
+}
+
+// When the signalling channel is ready, we can really kick off the allocator
+void P2PTransportChannel::OnSignalingReady() {
+ if (waiting_for_signaling_) {
+ waiting_for_signaling_ = false;
+ AddAllocatorSession(allocator_->CreateSession(name(), content_type()));
+ thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE);
+ }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/p2ptransportchannel.h b/talk/p2p/base/p2ptransportchannel.h
new file mode 100644
index 0000000..805e159
--- /dev/null
+++ b/talk/p2p/base/p2ptransportchannel.h
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+// P2PTransportChannel wraps up the state management of the connection between
+// two P2P clients. Clients have candidate ports for connecting, and
+// connections which are combinations of candidates from each end (Alice and
+// Bob each have candidates, one candidate from Alice and one candidate from
+// Bob are used to make a connection, repeat to make many connections).
+//
+// When all of the available connections become invalid (non-writable), we
+// kick off a process of determining more candidates and more connections.
+//
+#ifndef TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
+
+#include <map>
+#include <vector>
+#include <string>
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/p2ptransport.h"
+
+namespace cricket {
+
+// Adds the port on which the candidate originated.
+class RemoteCandidate : public Candidate {
+ public:
+ RemoteCandidate(const Candidate& c, Port* origin_port)
+ : Candidate(c), origin_port_(origin_port) {}
+
+ Port* origin_port() { return origin_port_; }
+
+ private:
+ Port* origin_port_;
+};
+
+// P2PTransportChannel manages the candidates and connection process to keep
+// two P2P clients connected to each other.
+class P2PTransportChannel : public TransportChannelImpl,
+ public talk_base::MessageHandler {
+ public:
+ P2PTransportChannel(const std::string &name,
+ const std::string &content_type,
+ P2PTransport* transport,
+ PortAllocator *allocator);
+ virtual ~P2PTransportChannel();
+
+ // From TransportChannelImpl:
+ virtual Transport* GetTransport() { return transport_; }
+ virtual void Connect();
+ virtual void Reset();
+ virtual void OnSignalingReady();
+
+ // From TransportChannel:
+ virtual int SendPacket(const char *data, size_t len);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError() { return error_; }
+
+ // This hack is here to allow the SocketMonitor to downcast to the
+ // P2PTransportChannel safely.
+ virtual P2PTransportChannel* GetP2PChannel() { return this; }
+
+ // These are used by the connection monitor.
+ sigslot::signal1<P2PTransportChannel*> SignalConnectionMonitor;
+ const std::vector<Connection *>& connections() const { return connections_; }
+ Connection* best_connection() const { return best_connection_; }
+
+ // Handler for internal messages.
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ virtual void OnCandidate(const Candidate& candidate);
+
+ private:
+ void Allocate();
+ void CancelPendingAllocate();
+ void UpdateConnectionStates();
+ void RequestSort();
+ void SortConnections();
+ void SwitchBestConnectionTo(Connection* conn);
+ void UpdateChannelState();
+ void HandleWritable();
+ void HandleNotWritable();
+ void HandleAllTimedOut();
+ Connection* GetBestConnectionOnNetwork(talk_base::Network* network);
+ bool CreateConnections(const Candidate &remote_candidate, Port* origin_port,
+ bool readable);
+ bool CreateConnection(Port* port, const Candidate& remote_candidate,
+ Port* origin_port, bool readable);
+ void RememberRemoteCandidate(const Candidate& remote_candidate,
+ Port* origin_port);
+ void OnUnknownAddress(Port *port, const talk_base::SocketAddress &addr,
+ StunMessage *stun_msg,
+ const std::string &remote_username);
+ void OnPortReady(PortAllocatorSession *session, Port* port);
+ void OnCandidatesReady(PortAllocatorSession *session,
+ const std::vector<Candidate>& candidates);
+ void OnConnectionStateChange(Connection *connection);
+ void OnConnectionDestroyed(Connection *connection);
+ void OnPortDestroyed(Port* port);
+ void OnReadPacket(Connection *connection, const char *data, size_t len);
+ void OnSort();
+ void OnPing();
+ bool IsPingable(Connection* conn);
+ Connection* FindNextPingableConnection();
+ uint32 NumPingableConnections();
+ PortAllocatorSession* allocator_session() {
+ return allocator_sessions_.back();
+ }
+ void AddAllocatorSession(PortAllocatorSession* session);
+
+ talk_base::Thread* thread() const { return worker_thread_; }
+
+ P2PTransport* transport_;
+ PortAllocator *allocator_;
+ talk_base::Thread *worker_thread_;
+ bool waiting_for_signaling_;
+ int error_;
+ std::vector<PortAllocatorSession*> allocator_sessions_;
+ std::vector<Port *> ports_;
+ std::vector<Connection *> connections_;
+ Connection *best_connection_;
+ std::vector<RemoteCandidate> remote_candidates_;
+ // indicates whether StartGetAllCandidates has been called
+ bool pinging_started_;
+ bool sort_dirty_; // indicates whether another sort is needed right now
+ bool was_writable_;
+ bool was_timed_out_;
+ typedef std::map<talk_base::Socket::Option, int> OptionMap;
+ OptionMap options_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(P2PTransportChannel);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_P2PTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/parsing.cc b/talk/p2p/base/parsing.cc
new file mode 100644
index 0000000..da4c31b
--- /dev/null
+++ b/talk/p2p/base/parsing.cc
@@ -0,0 +1,163 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/p2p/base/parsing.h"
+
+#include <algorithm>
+#include <stdlib.h>
+#include "talk/base/stringutils.h"
+
+namespace {
+std::string kTrue = "true";
+std::string kOne = "1";
+}
+
+namespace cricket {
+
+bool BadParse(const std::string& text, ParseError* err) {
+ if (err != NULL) {
+ err->text = text;
+ }
+ return false;
+}
+
+bool BadWrite(const std::string& text, WriteError* err) {
+ if (err != NULL) {
+ err->text = text;
+ }
+ return false;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const std::string& def) {
+ std::string val = elem->Attr(name);
+ return val.empty() ? def : val;
+}
+
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const char* def) {
+ return GetXmlAttr(elem, name, std::string(def));
+}
+
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, bool def) {
+ std::string val = elem->Attr(name);
+ std::transform(val.begin(), val.end(), val.begin(), tolower);
+
+ return val.empty() ? def : (val == kTrue || val == kOne);
+}
+
+int GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, int def) {
+ std::string val = elem->Attr(name);
+ return val.empty() ? def : atoi(val.c_str());
+}
+
+void AddXmlAttr(buzz::XmlElement* elem,
+ const buzz::QName& name, int n) {
+ char buf[32];
+ talk_base::sprintfn(buf, sizeof(buf), "%d", n);
+ elem->AddAttr(name, buf);
+}
+
+void SetXmlBody(buzz::XmlElement* elem, uint32 u) {
+ char buf[16];
+ talk_base::sprintfn(buf, sizeof(buf), "%u", u);
+ elem->SetBodyText(buf);
+}
+
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+ const std::string& name) {
+ for (const buzz::XmlElement* child = parent->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ if (child->Name().LocalPart() == name) {
+ return child;
+ }
+ }
+ return NULL;
+}
+
+bool RequireXmlChild(const buzz::XmlElement* parent,
+ const std::string& name,
+ const buzz::XmlElement** child,
+ ParseError* error) {
+ *child = GetXmlChild(parent, name);
+ if (*child == NULL) {
+ return BadParse("element '" + parent->Name().Merged() +
+ "' missing required child '" + name,
+ error);
+ } else {
+ return true;
+ }
+}
+
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ std::string* value,
+ ParseError* error) {
+ if (!elem->HasAttr(name)) {
+ return BadParse("element '" + elem->Name().Merged() +
+ "' missing required attribute '"
+ + name.Merged() + "'",
+ error);
+ } else {
+ *value = elem->Attr(name);
+ return true;
+ }
+}
+
+void AddXmlChildren(buzz::XmlElement* parent,
+ const std::vector<buzz::XmlElement*>& children) {
+ for (std::vector<buzz::XmlElement*>::const_iterator iter = children.begin();
+ iter != children.end();
+ iter++) {
+ parent->AddElement(*iter);
+ }
+}
+
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest) {
+ for (const buzz::XmlElement* child = source->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ dest->AddElement(new buzz::XmlElement(*child));
+ }
+}
+
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem) {
+ std::vector<buzz::XmlElement*> children;
+ for (const buzz::XmlElement* child = elem->FirstElement();
+ child != NULL;
+ child = child->NextElement()) {
+ children.push_back(new buzz::XmlElement(*child));
+ }
+ return children;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/parsing.h b/talk/p2p/base/parsing.h
new file mode 100644
index 0000000..805372b
--- /dev/null
+++ b/talk/p2p/base/parsing.h
@@ -0,0 +1,108 @@
+/*
+ * libjingle
+ * Copyright 2010, 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_P2P_BASE_PARSING_H_
+#define TALK_P2P_BASE_PARSING_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/xmllite/xmlelement.h" // Needed to delete ParseError.extra.
+
+namespace cricket {
+
+// We decided "bool Parse(in, out*, error*)" is generally the best
+// parse signature. "out Parse(in)" doesn't allow for errors.
+// "error* Parse(in, out*)" doesn't allow flexible memory management.
+
+// The error type for parsing.
+struct ParseError {
+ public:
+ // explains the error
+ std::string text;
+ // provide details about what wasn't parsable
+ const buzz::XmlElement* extra;
+
+ ParseError() : extra(NULL) {}
+
+ ~ParseError() {
+ delete extra;
+ }
+
+ void SetText(const std::string& text) {
+ this->text = text;
+ }
+};
+
+// The error type for writing.
+struct WriteError {
+ std::string text;
+
+ void SetText(const std::string& text) {
+ this->text = text;
+ }
+};
+
+// Convenience method for returning a message when parsing fails.
+bool BadParse(const std::string& text, ParseError* err);
+
+// Convenience method for returning a message when writing fails.
+bool BadWrite(const std::string& text, WriteError* error);
+
+// helper XML functions
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const std::string& def);
+std::string GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ const char* def);
+// Return true if the value is "true" or "1".
+bool GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, bool def);
+int GetXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name, int def);
+void AddXmlAttr(buzz::XmlElement* elem,
+ const buzz::QName& name, int n);
+void SetXmlBody(buzz::XmlElement* elem, uint32 u);
+const buzz::XmlElement* GetXmlChild(const buzz::XmlElement* parent,
+ const std::string& name);
+bool RequireXmlChild(const buzz::XmlElement* parent,
+ const std::string& name,
+ const buzz::XmlElement** child,
+ ParseError* error);
+bool RequireXmlAttr(const buzz::XmlElement* elem,
+ const buzz::QName& name,
+ std::string* value,
+ ParseError* error);
+void AddXmlChildren(buzz::XmlElement* parent,
+ const std::vector<buzz::XmlElement*>& children);
+void CopyXmlChildren(const buzz::XmlElement* source, buzz::XmlElement* dest);
+std::vector<buzz::XmlElement*> CopyOfXmlChildren(const buzz::XmlElement* elem);
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PARSING_H_
diff --git a/talk/p2p/base/port.cc b/talk/p2p/base/port.cc
new file mode 100644
index 0000000..0768e5b
--- /dev/null
+++ b/talk/p2p/base/port.cc
@@ -0,0 +1,895 @@
+/*
+ * 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 "talk/p2p/base/port.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/common.h"
+
+namespace {
+
+// The length of time we wait before timing out readability on a connection.
+const uint32 CONNECTION_READ_TIMEOUT = 30 * 1000; // 30 seconds
+
+// The length of time we wait before timing out writability on a connection.
+const uint32 CONNECTION_WRITE_TIMEOUT = 15 * 1000; // 15 seconds
+
+// The length of time we wait before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_TIMEOUT = 5 * 1000; // 5 seconds
+
+// The number of pings that must fail to respond before we become unwritable.
+const uint32 CONNECTION_WRITE_CONNECT_FAILURES = 5;
+
+// This is the length of time that we wait for a ping response to come back.
+const int CONNECTION_RESPONSE_TIMEOUT = 5 * 1000; // 5 seconds
+
+// Determines whether we have seen at least the given maximum number of
+// pings fail to have a response.
+inline bool TooManyFailures(
+ const std::vector<uint32>& pings_since_last_response,
+ uint32 maximum_failures,
+ uint32 rtt_estimate,
+ uint32 now) {
+
+ // If we haven't sent that many pings, then we can't have failed that many.
+ if (pings_since_last_response.size() < maximum_failures)
+ return false;
+
+ // Check if the window in which we would expect a response to the ping has
+ // already elapsed.
+ return pings_since_last_response[maximum_failures - 1] + rtt_estimate < now;
+}
+
+// Determines whether we have gone too long without seeing any response.
+inline bool TooLongWithoutResponse(
+ const std::vector<uint32>& pings_since_last_response,
+ uint32 maximum_time,
+ uint32 now) {
+
+ if (pings_since_last_response.size() == 0)
+ return false;
+
+ return pings_since_last_response[0] + maximum_time < now;
+}
+
+// We will restrict RTT estimates (when used for determining state) to be
+// within a reasonable range.
+const uint32 MINIMUM_RTT = 100; // 0.1 seconds
+const uint32 MAXIMUM_RTT = 3000; // 3 seconds
+
+// When we don't have any RTT data, we have to pick something reasonable. We
+// use a large value just in case the connection is really slow.
+const uint32 DEFAULT_RTT = MAXIMUM_RTT;
+
+// Computes our estimate of the RTT given the current estimate.
+inline uint32 ConservativeRTTEstimate(uint32 rtt) {
+ return talk_base::_max(MINIMUM_RTT, talk_base::_min(MAXIMUM_RTT, 2 * rtt));
+}
+
+// Weighting of the old rtt value to new data.
+const int RTT_RATIO = 3; // 3 : 1
+
+// The delay before we begin checking if this port is useless.
+const int kPortTimeoutDelay = 30 * 1000; // 30 seconds
+
+const uint32 MSG_CHECKTIMEOUT = 1;
+const uint32 MSG_DELETE = 1;
+}
+
+namespace cricket {
+
+static const char* const PROTO_NAMES[] = { "udp", "tcp", "ssltcp" };
+
+const char* ProtoToString(ProtocolType proto) {
+ return PROTO_NAMES[proto];
+}
+
+bool StringToProto(const char* value, ProtocolType* proto) {
+ for (size_t i = 0; i <= PROTO_LAST; ++i) {
+ if (strcmp(PROTO_NAMES[i], value) == 0) {
+ *proto = static_cast<ProtocolType>(i);
+ return true;
+ }
+ }
+ return false;
+}
+
+Port::Port(talk_base::Thread* thread, const std::string& type,
+ talk_base::PacketSocketFactory* factory, talk_base::Network* network,
+ uint32 ip, int min_port, int max_port)
+ : thread_(thread),
+ factory_(factory),
+ type_(type),
+ network_(network),
+ ip_(ip),
+ min_port_(min_port),
+ max_port_(max_port),
+ preference_(-1),
+ lifetime_(LT_PRESTART),
+ enable_port_packets_(false) {
+ ASSERT(factory_ != NULL);
+
+ set_username_fragment(talk_base::CreateRandomString(16));
+ set_password(talk_base::CreateRandomString(16));
+ LOG_J(LS_INFO, this) << "Port created";
+}
+
+Port::~Port() {
+ // Delete all of the remaining connections. We copy the list up front
+ // because each deletion will cause it to be modified.
+
+ std::vector<Connection*> list;
+
+ AddressMap::iterator iter = connections_.begin();
+ while (iter != connections_.end()) {
+ list.push_back(iter->second);
+ ++iter;
+ }
+
+ for (uint32 i = 0; i < list.size(); i++)
+ delete list[i];
+}
+
+Connection* Port::GetConnection(const talk_base::SocketAddress& remote_addr) {
+ AddressMap::const_iterator iter = connections_.find(remote_addr);
+ if (iter != connections_.end())
+ return iter->second;
+ else
+ return NULL;
+}
+
+void Port::AddAddress(const talk_base::SocketAddress& address,
+ const std::string& protocol,
+ bool final) {
+ Candidate c;
+ c.set_name(name_);
+ c.set_type(type_);
+ c.set_protocol(protocol);
+ c.set_address(address);
+ c.set_preference(preference_);
+ c.set_username(username_frag_);
+ c.set_password(password_);
+ c.set_network_name(network_->name());
+ c.set_generation(generation_);
+ candidates_.push_back(c);
+
+ if (final)
+ SignalAddressReady(this);
+}
+
+void Port::AddConnection(Connection* conn) {
+ connections_[conn->remote_candidate().address()] = conn;
+ conn->SignalDestroyed.connect(this, &Port::OnConnectionDestroyed);
+ SignalConnectionCreated(this, conn);
+}
+
+void Port::OnReadPacket(
+ const char* data, size_t size, const talk_base::SocketAddress& addr) {
+ // If the user has enabled port packets, just hand this over.
+ if (enable_port_packets_) {
+ SignalReadPacket(this, data, size, addr);
+ return;
+ }
+
+ // If this is an authenticated STUN request, then signal unknown address and
+ // send back a proper binding response.
+ StunMessage* msg;
+ std::string remote_username;
+ if (!GetStunMessage(data, size, addr, &msg, &remote_username)) {
+ LOG_J(LS_ERROR, this) << "Received non-STUN packet from unknown address ("
+ << addr.ToString() << ")";
+ } else if (!msg) {
+ // STUN message handled already
+ } else if (msg->type() == STUN_BINDING_REQUEST) {
+ SignalUnknownAddress(this, addr, msg, remote_username);
+ } else {
+ // NOTE(tschmelcher): This is benign. It occurs if we pruned a
+ // connection for this port while it had STUN requests in flight, because
+ // we then get back responses for them, which this code correctly does not
+ // handle.
+ LOG_J(LS_ERROR, this) << "Received unexpected STUN message type ("
+ << msg->type() << ") from unknown address ("
+ << addr.ToString() << ")";
+ delete msg;
+ }
+}
+
+bool Port::GetStunMessage(const char* data, size_t size,
+ const talk_base::SocketAddress& addr,
+ StunMessage** out_msg, std::string* out_username) {
+ // NOTE: This could clearly be optimized to avoid allocating any memory.
+ // However, at the data rates we'll be looking at on the client side,
+ // this probably isn't worth worrying about.
+ ASSERT(out_msg != NULL);
+ ASSERT(out_username != NULL);
+ *out_msg = NULL;
+ out_username->clear();
+
+ // Parse the request message. If the packet is not a complete and correct
+ // STUN message, then ignore it.
+ talk_base::scoped_ptr<StunMessage> stun_msg(new StunMessage());
+ talk_base::ByteBuffer buf(data, size);
+ if (!stun_msg->Read(&buf) || (buf.Length() > 0)) {
+ return false;
+ }
+
+ // The packet must include a username that either begins or ends with our
+ // fragment. It should begin with our fragment if it is a request and it
+ // should end with our fragment if it is a response.
+ const StunByteStringAttribute* username_attr =
+ stun_msg->GetByteString(STUN_ATTR_USERNAME);
+
+ int remote_frag_len = (username_attr ? username_attr->length() : 0);
+ remote_frag_len -= static_cast<int>(username_frag_.size());
+
+ if (stun_msg->type() == STUN_BINDING_REQUEST) {
+ if (remote_frag_len < 0) {
+ // Username not present or corrupted, don't reply.
+ LOG_J(LS_ERROR, this) << "Received STUN request without username from "
+ << addr.ToString();
+ return true;
+ } else if (std::memcmp(username_attr->bytes(), username_frag_.c_str(),
+ username_frag_.size()) != 0) {
+ LOG_J(LS_ERROR, this) << "Received STUN request with bad local username "
+ << std::string(username_attr->bytes(),
+ username_attr->length()) << " from "
+ << addr.ToString();
+ SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ return true;
+ }
+
+ out_username->assign(username_attr->bytes() + username_frag_.size(),
+ username_attr->bytes() + username_attr->length());
+ } else if ((stun_msg->type() == STUN_BINDING_RESPONSE)
+ || (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE)) {
+ if (remote_frag_len < 0) {
+ LOG_J(LS_ERROR, this) << "Received STUN response without username from "
+ << addr.ToString();
+ // Do not send error response to a response
+ return true;
+ } else if (std::memcmp(username_attr->bytes() + remote_frag_len,
+ username_frag_.c_str(),
+ username_frag_.size()) != 0) {
+ LOG_J(LS_ERROR, this) << "Received STUN response with bad local username "
+ << std::string(username_attr->bytes(),
+ username_attr->length()) << " from "
+ << addr.ToString();
+ // Do not send error response to a response
+ return true;
+ }
+
+ out_username->assign(username_attr->bytes(),
+ username_attr->bytes() + remote_frag_len);
+
+ if (stun_msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+ if (const StunErrorCodeAttribute* error_code = stun_msg->GetErrorCode()) {
+ LOG_J(LS_ERROR, this) << "Received STUN binding error:"
+ << " class="
+ << static_cast<int>(error_code->error_class())
+ << " number="
+ << static_cast<int>(error_code->number())
+ << " reason='" << error_code->reason() << "'"
+ << " from " << addr.ToString();
+ // Return message to allow error-specific processing
+ } else {
+ LOG_J(LS_ERROR, this) << "Received STUN binding error without a error "
+ << "code from " << addr.ToString();
+ // Drop corrupt message
+ return true;
+ }
+ }
+ } else {
+ LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type ("
+ << stun_msg->type() << ") from " << addr.ToString();
+ return true;
+ }
+
+ // Return the STUN message found.
+ *out_msg = stun_msg.release();
+ return true;
+}
+
+void Port::SendBindingResponse(StunMessage* request,
+ const talk_base::SocketAddress& addr) {
+ ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+ // Retrieve the username from the request.
+ const StunByteStringAttribute* username_attr =
+ request->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT(username_attr != NULL);
+ if (username_attr == NULL) {
+ // No valid username, skip the response.
+ return;
+ }
+
+ // Fill in the response message.
+ StunMessage response;
+ response.SetType(STUN_BINDING_RESPONSE);
+ response.SetTransactionID(request->transaction_id());
+
+ StunByteStringAttribute* username2_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username2_attr->CopyBytes(username_attr->bytes(), username_attr->length());
+ response.AddAttribute(username2_attr);
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ addr_attr->SetFamily(1);
+ addr_attr->SetPort(addr.port());
+ addr_attr->SetIP(addr.ip());
+ response.AddAttribute(addr_attr);
+
+ // Send the response message.
+ // NOTE: If we wanted to, this is where we would add the HMAC.
+ talk_base::ByteBuffer buf;
+ response.Write(&buf);
+ if (SendTo(buf.Data(), buf.Length(), addr, false) < 0) {
+ LOG_J(LS_ERROR, this) << "Failed to send STUN ping response to "
+ << addr.ToString();
+ }
+
+ // The fact that we received a successful request means that this connection
+ // (if one exists) should now be readable.
+ Connection* conn = GetConnection(addr);
+ ASSERT(conn != NULL);
+ if (conn)
+ conn->ReceivedPing();
+}
+
+void Port::SendBindingErrorResponse(StunMessage* request,
+ const talk_base::SocketAddress& addr,
+ int error_code, const std::string& reason) {
+ ASSERT(request->type() == STUN_BINDING_REQUEST);
+
+ // Retrieve the username from the request. If it didn't have one, we
+ // shouldn't be responding at all.
+ const StunByteStringAttribute* username_attr =
+ request->GetByteString(STUN_ATTR_USERNAME);
+ ASSERT(username_attr != NULL);
+ if (username_attr == NULL) {
+ // No valid username, skip the response.
+ return;
+ }
+
+ // Fill in the response message.
+ StunMessage response;
+ response.SetType(STUN_BINDING_ERROR_RESPONSE);
+ response.SetTransactionID(request->transaction_id());
+
+ StunByteStringAttribute* username2_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username2_attr->CopyBytes(username_attr->bytes(), username_attr->length());
+ response.AddAttribute(username2_attr);
+
+ StunErrorCodeAttribute* error_attr = StunAttribute::CreateErrorCode();
+ error_attr->SetErrorCode(error_code);
+ error_attr->SetReason(reason);
+ response.AddAttribute(error_attr);
+
+ // Send the response message.
+ // NOTE: If we wanted to, this is where we would add the HMAC.
+ talk_base::ByteBuffer buf;
+ response.Write(&buf);
+ SendTo(buf.Data(), buf.Length(), addr, false);
+ LOG_J(LS_INFO, this) << "Sending STUN binding error: reason=" << reason
+ << " to " << addr.ToString();
+}
+
+void Port::OnMessage(talk_base::Message *pmsg) {
+ ASSERT(pmsg->message_id == MSG_CHECKTIMEOUT);
+ ASSERT(lifetime_ == LT_PRETIMEOUT);
+ lifetime_ = LT_POSTTIMEOUT;
+ CheckTimeout();
+}
+
+std::string Port::ToString() const {
+ std::stringstream ss;
+ ss << "Port[" << name_ << ":" << type_ << ":" << network_->ToString() << "]";
+ return ss.str();
+}
+
+void Port::EnablePortPackets() {
+ enable_port_packets_ = true;
+}
+
+void Port::Start() {
+ // The port sticks around for a minimum lifetime, after which
+ // we destroy it when it drops to zero connections.
+ if (lifetime_ == LT_PRESTART) {
+ lifetime_ = LT_PRETIMEOUT;
+ thread_->PostDelayed(kPortTimeoutDelay, this, MSG_CHECKTIMEOUT);
+ } else {
+ LOG_J(LS_WARNING, this) << "Port restart attempted";
+ }
+}
+
+void Port::OnConnectionDestroyed(Connection* conn) {
+ AddressMap::iterator iter =
+ connections_.find(conn->remote_candidate().address());
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+
+ CheckTimeout();
+}
+
+void Port::Destroy() {
+ ASSERT(connections_.empty());
+ LOG_J(LS_INFO, this) << "Port deleted";
+ SignalDestroyed(this);
+ delete this;
+}
+
+void Port::CheckTimeout() {
+ // If this port has no connections, then there's no reason to keep it around.
+ // When the connections time out (both read and write), they will delete
+ // themselves, so if we have any connections, they are either readable or
+ // writable (or still connecting).
+ if ((lifetime_ == LT_POSTTIMEOUT) && connections_.empty()) {
+ Destroy();
+ }
+}
+
+// A ConnectionRequest is a simple STUN ping used to determine writability.
+class ConnectionRequest : public StunRequest {
+ public:
+ explicit ConnectionRequest(Connection* connection) : connection_(connection) {
+ }
+
+ virtual ~ConnectionRequest() {
+ }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ StunByteStringAttribute* username_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ std::string username = connection_->remote_candidate().username();
+ username.append(connection_->port()->username_fragment());
+ username_attr->CopyBytes(username.c_str(), username.size());
+ request->AddAttribute(username_attr);
+ }
+
+ virtual void OnResponse(StunMessage* response) {
+ connection_->OnConnectionRequestResponse(this, response);
+ }
+
+ virtual void OnErrorResponse(StunMessage* response) {
+ connection_->OnConnectionRequestErrorResponse(this, response);
+ }
+
+ virtual void OnTimeout() {
+ connection_->OnConnectionRequestTimeout(this);
+ }
+
+ virtual int GetNextDelay() {
+ // Each request is sent only once. After a single delay , the request will
+ // time out.
+ timeout_ = true;
+ return CONNECTION_RESPONSE_TIMEOUT;
+ }
+
+ private:
+ Connection* connection_;
+};
+
+//
+// Connection
+//
+
+Connection::Connection(Port* port, size_t index,
+ const Candidate& remote_candidate)
+ : port_(port), local_candidate_index_(index),
+ remote_candidate_(remote_candidate), read_state_(STATE_READ_TIMEOUT),
+ write_state_(STATE_WRITE_CONNECT), connected_(true), pruned_(false),
+ requests_(port->thread()), rtt_(DEFAULT_RTT),
+ last_ping_sent_(0), last_ping_received_(0), last_data_received_(0),
+ reported_(false) {
+ // Wire up to send stun packets
+ requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket);
+ LOG_J(LS_INFO, this) << "Connection created";
+}
+
+Connection::~Connection() {
+}
+
+const Candidate& Connection::local_candidate() const {
+ if (local_candidate_index_ < port_->candidates().size())
+ return port_->candidates()[local_candidate_index_];
+ ASSERT(false);
+ static Candidate foo;
+ return foo;
+}
+
+void Connection::set_read_state(ReadState value) {
+ ReadState old_value = read_state_;
+ read_state_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_read_state";
+ SignalStateChange(this);
+ CheckTimeout();
+ }
+}
+
+void Connection::set_write_state(WriteState value) {
+ WriteState old_value = write_state_;
+ write_state_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_write_state";
+ SignalStateChange(this);
+ CheckTimeout();
+ }
+}
+
+void Connection::set_connected(bool value) {
+ bool old_value = connected_;
+ connected_ = value;
+ if (value != old_value) {
+ LOG_J(LS_VERBOSE, this) << "set_connected";
+ }
+}
+
+void Connection::OnSendStunPacket(const void* data, size_t size,
+ StunRequest* req) {
+ if (port_->SendTo(data, size, remote_candidate_.address(), false) < 0) {
+ LOG_J(LS_WARNING, this) << "Failed to send STUN ping " << req->id();
+ }
+}
+
+void Connection::OnReadPacket(const char* data, size_t size) {
+ StunMessage* msg;
+ std::string remote_username;
+ const talk_base::SocketAddress& addr(remote_candidate_.address());
+ if (!port_->GetStunMessage(data, size, addr, &msg, &remote_username)) {
+ // The packet did not parse as a valid STUN message
+
+ // If this connection is readable, then pass along the packet.
+ if (read_state_ == STATE_READABLE) {
+ // readable means data from this address is acceptable
+ // Send it on!
+
+ last_data_received_ = talk_base::Time();
+ recv_rate_tracker_.Update(size);
+ SignalReadPacket(this, data, size);
+
+ // If timed out sending writability checks, start up again
+ if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+ set_write_state(STATE_WRITE_CONNECT);
+ } else {
+ // Not readable means the remote address hasn't sent a valid
+ // binding request yet.
+
+ LOG_J(LS_WARNING, this)
+ << "Received non-STUN packet from an unreadable connection.";
+ }
+ } else if (!msg) {
+ // The packet was STUN, but was already handled internally.
+ } else if (remote_username != remote_candidate_.username()) {
+ // The packet had the right local username, but the remote username was
+ // not the right one for the remote address.
+ if (msg->type() == STUN_BINDING_REQUEST) {
+ LOG_J(LS_ERROR, this) << "Received STUN request with bad remote username "
+ << remote_username;
+ port_->SendBindingErrorResponse(msg, addr, STUN_ERROR_BAD_REQUEST,
+ STUN_ERROR_REASON_BAD_REQUEST);
+ } else if (msg->type() == STUN_BINDING_RESPONSE ||
+ msg->type() == STUN_BINDING_ERROR_RESPONSE) {
+ LOG_J(LS_ERROR, this) << "Received STUN response with bad remote username"
+ " " << remote_username;
+ }
+ delete msg;
+ } else {
+ // The packet is STUN, with the right username.
+ // If this is a STUN request, then update the readable bit and respond.
+ // If this is a STUN response, then update the writable bit.
+
+ switch (msg->type()) {
+ case STUN_BINDING_REQUEST:
+ // Incoming, validated stun request from remote peer.
+ // This call will also set the connection readable.
+
+ port_->SendBindingResponse(msg, addr);
+
+ // If timed out sending writability checks, start up again
+ if (!pruned_ && (write_state_ == STATE_WRITE_TIMEOUT))
+ set_write_state(STATE_WRITE_CONNECT);
+ break;
+
+ case STUN_BINDING_RESPONSE:
+ case STUN_BINDING_ERROR_RESPONSE:
+ // Response from remote peer. Does it match request sent?
+ // This doesn't just check, it makes callbacks if transaction
+ // id's match
+ requests_.CheckResponse(msg);
+ break;
+
+ default:
+ ASSERT(false);
+ break;
+ }
+
+ // Done with the message; delete
+
+ delete msg;
+ }
+}
+
+void Connection::Prune() {
+ if (!pruned_) {
+ LOG_J(LS_VERBOSE, this) << "Connection pruned";
+ pruned_ = true;
+ requests_.Clear();
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::Destroy() {
+ LOG_J(LS_VERBOSE, this) << "Connection destroyed";
+ set_read_state(STATE_READ_TIMEOUT);
+ set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void Connection::UpdateState(uint32 now) {
+ uint32 rtt = ConservativeRTTEstimate(rtt_);
+
+ std::string pings;
+ for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+ char buf[32];
+ talk_base::sprintfn(buf, sizeof(buf), "%u",
+ pings_since_last_response_[i]);
+ pings.append(buf).append(" ");
+ }
+ LOG_J(LS_VERBOSE, this) << "UpdateState(): pings_since_last_response_=" <<
+ pings << ", rtt=" << rtt << ", now=" << now;
+
+ // Check the readable state.
+ //
+ // Since we don't know how many pings the other side has attempted, the best
+ // test we can do is a simple window.
+
+ if ((read_state_ == STATE_READABLE) &&
+ (last_ping_received_ + CONNECTION_READ_TIMEOUT <= now)) {
+ LOG_J(LS_INFO, this) << "Unreadable after "
+ << now - last_ping_received_
+ << " ms without a ping, rtt=" << rtt;
+ set_read_state(STATE_READ_TIMEOUT);
+ }
+
+ // Check the writable state. (The order of these checks is important.)
+ //
+ // Before becoming unwritable, we allow for a fixed number of pings to fail
+ // (i.e., receive no response). We also have to give the response time to
+ // get back, so we include a conservative estimate of this.
+ //
+ // Before timing out writability, we give a fixed amount of time. This is to
+ // allow for changes in network conditions.
+
+ if ((write_state_ == STATE_WRITABLE) &&
+ TooManyFailures(pings_since_last_response_,
+ CONNECTION_WRITE_CONNECT_FAILURES,
+ rtt,
+ now) &&
+ TooLongWithoutResponse(pings_since_last_response_,
+ CONNECTION_WRITE_CONNECT_TIMEOUT,
+ now)) {
+ uint32 max_pings = CONNECTION_WRITE_CONNECT_FAILURES;
+ LOG_J(LS_INFO, this) << "Unwritable after " << max_pings
+ << " ping failures and "
+ << now - pings_since_last_response_[0]
+ << " ms without a response,"
+ << " ms since last received ping="
+ << now - last_ping_received_
+ << " ms since last received data="
+ << now - last_data_received_
+ << " rtt=" << rtt;
+ set_write_state(STATE_WRITE_CONNECT);
+ }
+
+ if ((write_state_ == STATE_WRITE_CONNECT) &&
+ TooLongWithoutResponse(pings_since_last_response_,
+ CONNECTION_WRITE_TIMEOUT,
+ now)) {
+ LOG_J(LS_INFO, this) << "Timed out after "
+ << now - pings_since_last_response_[0]
+ << " ms without a response, rtt=" << rtt;
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::Ping(uint32 now) {
+ ASSERT(connected_);
+ last_ping_sent_ = now;
+ pings_since_last_response_.push_back(now);
+ ConnectionRequest *req = new ConnectionRequest(this);
+ LOG_J(LS_VERBOSE, this) << "Sending STUN ping " << req->id() << " at " << now;
+ requests_.Send(req);
+}
+
+void Connection::ReceivedPing() {
+ last_ping_received_ = talk_base::Time();
+ set_read_state(STATE_READABLE);
+}
+
+std::string Connection::ToString() const {
+ const char CONNECT_STATE_ABBREV[2] = {
+ '-', // not connected (false)
+ 'C', // connected (true)
+ };
+ const char READ_STATE_ABBREV[2] = {
+ 'R', // STATE_READABLE
+ '-', // STATE_READ_TIMEOUT
+ };
+ const char WRITE_STATE_ABBREV[3] = {
+ 'W', // STATE_WRITABLE
+ 'w', // STATE_WRITE_CONNECT
+ '-', // STATE_WRITE_TIMEOUT
+ };
+ const Candidate& local = local_candidate();
+ const Candidate& remote = remote_candidate();
+ std::stringstream ss;
+ ss << "Conn[" << local.generation()
+ << ":" << local.name() << ":" << local.type() << ":"
+ << local.protocol() << ":" << local.address().ToString()
+ << "->" << remote.name() << ":" << remote.type() << ":"
+ << remote.protocol() << ":" << remote.address().ToString()
+ << "|"
+ << CONNECT_STATE_ABBREV[connected()]
+ << READ_STATE_ABBREV[read_state()]
+ << WRITE_STATE_ABBREV[write_state()]
+ << "|";
+ if (rtt_ < DEFAULT_RTT) {
+ ss << rtt_ << "]";
+ } else {
+ ss << "-]";
+ }
+ return ss.str();
+}
+
+void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ // We've already validated that this is a STUN binding response with
+ // the correct local and remote username for this connection.
+ // So if we're not already, become writable. We may be bringing a pruned
+ // connection back to life, but if we don't really want it, we can always
+ // prune it again.
+ uint32 rtt = request->Elapsed();
+ set_write_state(STATE_WRITABLE);
+
+ std::string pings;
+ for (size_t i = 0; i < pings_since_last_response_.size(); ++i) {
+ char buf[32];
+ talk_base::sprintfn(buf, sizeof(buf), "%u",
+ pings_since_last_response_[i]);
+ pings.append(buf).append(" ");
+ }
+
+ LOG_J(LS_VERBOSE, this) << "Received STUN ping response " << request->id()
+ << ", pings_since_last_response_=" << pings
+ << ", rtt=" << rtt;
+
+ pings_since_last_response_.clear();
+ rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
+}
+
+void Connection::OnConnectionRequestErrorResponse(ConnectionRequest* request,
+ StunMessage* response) {
+ const StunErrorCodeAttribute* error = response->GetErrorCode();
+ uint32 error_code = error ?
+ error->error_code() : static_cast<uint32>(STUN_ERROR_GLOBAL_FAILURE);
+
+ if ((error_code == STUN_ERROR_UNKNOWN_ATTRIBUTE)
+ || (error_code == STUN_ERROR_SERVER_ERROR)
+ || (error_code == STUN_ERROR_UNAUTHORIZED)) {
+ // Recoverable error, retry
+ } else if (error_code == STUN_ERROR_STALE_CREDENTIALS) {
+ // Race failure, retry
+ } else {
+ // This is not a valid connection.
+ LOG_J(LS_ERROR, this) << "Received STUN error response, code="
+ << error_code << "; killing connection";
+ set_write_state(STATE_WRITE_TIMEOUT);
+ }
+}
+
+void Connection::OnConnectionRequestTimeout(ConnectionRequest* request) {
+ // Log at LS_INFO if we miss a ping on a writable connection.
+ talk_base::LoggingSeverity sev = (write_state_ == STATE_WRITABLE) ?
+ talk_base::LS_INFO : talk_base::LS_VERBOSE;
+ uint32 when = talk_base::Time() - request->Elapsed();
+ size_t failures;
+ for (failures = 0; failures < pings_since_last_response_.size(); ++failures) {
+ if (pings_since_last_response_[failures] > when) {
+ break;
+ }
+ }
+ ASSERT(failures > 0);
+ LOG_JV(sev, this) << "Timing-out STUN ping " << request->id()
+ << " after " << request->Elapsed()
+ << " ms, failures=" << failures;
+}
+
+void Connection::CheckTimeout() {
+ // If both read and write have timed out, then this connection can contribute
+ // no more to p2p socket unless at some later date readability were to come
+ // back. However, we gave readability a long time to timeout, so at this
+ // point, it seems fair to get rid of this connection.
+ if ((read_state_ == STATE_READ_TIMEOUT) &&
+ (write_state_ == STATE_WRITE_TIMEOUT)) {
+ port_->thread()->Post(this, MSG_DELETE);
+ }
+}
+
+void Connection::OnMessage(talk_base::Message *pmsg) {
+ ASSERT(pmsg->message_id == MSG_DELETE);
+
+ LOG_J(LS_INFO, this) << "Connection deleted";
+ SignalDestroyed(this);
+ delete this;
+}
+
+size_t Connection::recv_bytes_second() {
+ return recv_rate_tracker_.units_second();
+}
+
+size_t Connection::recv_total_bytes() {
+ return recv_rate_tracker_.total_units();
+}
+
+size_t Connection::sent_bytes_second() {
+ return send_rate_tracker_.units_second();
+}
+
+size_t Connection::sent_total_bytes() {
+ return send_rate_tracker_.total_units();
+}
+
+ProxyConnection::ProxyConnection(Port* port, size_t index,
+ const Candidate& candidate)
+ : Connection(port, index, candidate), error_(0) {
+}
+
+int ProxyConnection::Send(const void* data, size_t size) {
+ if (write_state() != STATE_WRITABLE) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ int sent = port_->SendTo(data, size, remote_candidate_.address(), true);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = port_->GetError();
+ } else {
+ send_rate_tracker_.Update(sent);
+ }
+ return sent;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/port.h b/talk/p2p/base/port.h
new file mode 100644
index 0000000..8304146
--- /dev/null
+++ b/talk/p2p/base/port.h
@@ -0,0 +1,424 @@
+/*
+ * 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_P2P_BASE_PORT_H_
+#define TALK_P2P_BASE_PORT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/network.h"
+#include "talk/base/packetsocketfactory.h"
+#include "talk/base/proxyinfo.h"
+#include "talk/base/ratetracker.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socketaddress.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/stun.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace talk_base {
+class AsyncPacketSocket;
+}
+
+namespace cricket {
+
+class Connection;
+class ConnectionRequest;
+
+enum ProtocolType {
+ PROTO_UDP,
+ PROTO_TCP,
+ PROTO_SSLTCP,
+ PROTO_LAST = PROTO_SSLTCP
+};
+
+const char* ProtoToString(ProtocolType proto);
+bool StringToProto(const char* value, ProtocolType* proto);
+
+struct ProtocolAddress {
+ talk_base::SocketAddress address;
+ ProtocolType proto;
+
+ ProtocolAddress(const talk_base::SocketAddress& a, ProtocolType p)
+ : address(a), proto(p) { }
+};
+
+// Represents a local communication mechanism that can be used to create
+// connections to similar mechanisms of the other client. Subclasses of this
+// one add support for specific mechanisms like local UDP ports.
+class Port : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+ Port(talk_base::Thread* thread, const std::string& type,
+ talk_base::PacketSocketFactory* factory, talk_base::Network* network,
+ uint32 ip, int min_port, int max_port);
+ virtual ~Port();
+
+ // The thread on which this port performs its I/O.
+ talk_base::Thread* thread() { return thread_; }
+
+ // The factory used to create the sockets of this port.
+ talk_base::PacketSocketFactory* socket_factory() const { return factory_; }
+ void set_socket_factory(talk_base::PacketSocketFactory* factory) {
+ factory_ = factory;
+ }
+
+ // Each port is identified by a name (for debugging purposes).
+ const std::string& name() const { return name_; }
+ void set_name(const std::string& name) { name_ = name; }
+
+ // In order to establish a connection to this Port (so that real data can be
+ // sent through), the other side must send us a STUN binding request that is
+ // authenticated with this username and password.
+ // Fills in the username fragment and password. These will be initially set
+ // in the constructor to random values. Subclasses or tests can override.
+ // TODO: Change this to "username" rather than "username_fragment".
+ const std::string& username_fragment() const { return username_frag_; }
+ void set_username_fragment(const std::string& username) {
+ username_frag_ = username;
+ }
+
+ const std::string& password() const { return password_; }
+ void set_password(const std::string& password) { password_ = password; }
+
+
+ // A value in [0,1] that indicates the preference for this port versus other
+ // ports on this client. (Larger indicates more preference.)
+ float preference() const { return preference_; }
+ void set_preference(float preference) { preference_ = preference; }
+
+ // Identifies the port type.
+ const std::string& type() const { return type_; }
+
+ // Identifies network that this port was allocated on.
+ talk_base::Network* network() { return network_; }
+
+ // Identifies the generation that this port was created in.
+ uint32 generation() { return generation_; }
+ void set_generation(uint32 generation) { generation_ = generation; }
+
+ // PrepareAddress will attempt to get an address for this port that other
+ // clients can send to. It may take some time before the address is read.
+ // Once it is ready, we will send SignalAddressReady. If errors are
+ // preventing the port from getting an address, it may send
+ // SignalAddressError.
+ virtual void PrepareAddress() = 0;
+ sigslot::signal1<Port*> SignalAddressReady;
+ sigslot::signal1<Port*> SignalAddressError;
+
+ // Provides all of the above information in one handy object.
+ const std::vector<Candidate>& candidates() const { return candidates_; }
+
+ // Returns a map containing all of the connections of this port, keyed by the
+ // remote address.
+ typedef std::map<talk_base::SocketAddress, Connection*> AddressMap;
+ const AddressMap& connections() { return connections_; }
+
+ // Returns the connection to the given address or NULL if none exists.
+ Connection* GetConnection(const talk_base::SocketAddress& remote_addr);
+
+ // Creates a new connection to the given address.
+ enum CandidateOrigin { ORIGIN_THIS_PORT, ORIGIN_OTHER_PORT, ORIGIN_MESSAGE };
+ virtual Connection* CreateConnection(const Candidate& remote_candidate,
+ CandidateOrigin origin) = 0;
+
+ // Called each time a connection is created.
+ sigslot::signal2<Port*, Connection*> SignalConnectionCreated;
+
+ // Sends the given packet to the given address, provided that the address is
+ // that of a connection or an address that has sent to us already.
+ virtual int SendTo(
+ const void* data, size_t size, const talk_base::SocketAddress& addr,
+ bool payload) = 0;
+
+ // Indicates that we received a successful STUN binding request from an
+ // address that doesn't correspond to any current connection. To turn this
+ // into a real connection, call CreateConnection.
+ sigslot::signal4<Port*, const talk_base::SocketAddress&, StunMessage*,
+ const std::string&> SignalUnknownAddress;
+
+ // Sends a response message (normal or error) to the given request. One of
+ // these methods should be called as a response to SignalUnknownAddress.
+ // NOTE: You MUST call CreateConnection BEFORE SendBindingResponse.
+ void SendBindingResponse(StunMessage* request,
+ const talk_base::SocketAddress& addr);
+ void SendBindingErrorResponse(
+ StunMessage* request, const talk_base::SocketAddress& addr,
+ int error_code, const std::string& reason);
+
+ // Indicates that errors occurred when performing I/O.
+ sigslot::signal2<Port*, int> SignalReadError;
+ sigslot::signal2<Port*, int> SignalWriteError;
+
+ // Functions on the underlying socket(s).
+ virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+ virtual int GetError() = 0;
+
+ void set_proxy(const std::string& user_agent,
+ const talk_base::ProxyInfo& proxy) {
+ user_agent_ = user_agent;
+ proxy_ = proxy;
+ }
+ const std::string& user_agent() { return user_agent_; }
+ const talk_base::ProxyInfo& proxy() { return proxy_; }
+
+ // Normally, packets arrive through a connection (or they result signaling of
+ // unknown address). Calling this method turns off delivery of packets
+ // through their respective connection and instead delivers every packet
+ // through this port.
+ void EnablePortPackets();
+ sigslot::signal4<Port*, const char*, size_t, const talk_base::SocketAddress&>
+ SignalReadPacket;
+
+ // Indicates to the port that its official use has now begun. This will
+ // start the timer that checks to see if the port is being used.
+ void Start();
+
+ // Called if the port has no connections and is no longer useful.
+ void Destroy();
+
+ // Signaled when this port decides to delete itself because it no longer has
+ // any usefulness.
+ sigslot::signal1<Port*> SignalDestroyed;
+
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ // Debugging description of this port
+ std::string ToString() const;
+
+ protected:
+ // Fills in the local address of the port.
+ void AddAddress(const talk_base::SocketAddress& address,
+ const std::string& protocol, bool final);
+
+ // Adds the given connection to the list. (Deleting removes them.)
+ void AddConnection(Connection* conn);
+
+ // Called when a packet is received from an unknown address that is not
+ // currently a connection. If this is an authenticated STUN binding request,
+ // then we will signal the client.
+ void OnReadPacket(const char* data, size_t size,
+ const talk_base::SocketAddress& addr);
+
+
+ // If the given data comprises a complete and correct STUN message then the
+ // return value is true, otherwise false. If the message username corresponds
+ // with this port's username fragment, msg will contain the parsed STUN
+ // message. Otherwise, the function may send a STUN response internally.
+ // remote_username contains the remote fragment of the STUN username.
+ bool GetStunMessage(const char* data, size_t size,
+ const talk_base::SocketAddress& addr,
+ StunMessage** out_msg, std::string* out_username);
+
+ // TODO: make these members private
+ talk_base::Thread* thread_;
+ talk_base::PacketSocketFactory* factory_;
+ std::string type_;
+ talk_base::Network* network_;
+ uint32 ip_;
+ int min_port_;
+ int max_port_;
+ uint32 generation_;
+ std::string name_;
+ std::string username_frag_;
+ std::string password_;
+ float preference_;
+ std::vector<Candidate> candidates_;
+ AddressMap connections_;
+ enum Lifetime { LT_PRESTART, LT_PRETIMEOUT, LT_POSTTIMEOUT } lifetime_;
+ bool enable_port_packets_;
+
+ private:
+ // Called when one of our connections deletes itself.
+ void OnConnectionDestroyed(Connection* conn);
+
+ // Checks if this port is useless, and hence, should be destroyed.
+ void CheckTimeout();
+
+ // Information to use when going through a proxy.
+ std::string user_agent_;
+ talk_base::ProxyInfo proxy_;
+
+ friend class Connection;
+};
+
+// Represents a communication link between a port on the local client and a
+// port on the remote client.
+class Connection : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ virtual ~Connection();
+
+ // The local port where this connection sends and receives packets.
+ Port* port() { return port_; }
+ const Port* port() const { return port_; }
+
+ // Returns the description of the local port
+ virtual const Candidate& local_candidate() const;
+
+ // Returns the description of the remote port to which we communicate.
+ const Candidate& remote_candidate() const { return remote_candidate_; }
+
+ enum ReadState {
+ STATE_READABLE = 0, // we have received pings recently
+ STATE_READ_TIMEOUT = 1 // we haven't received pings in a while
+ };
+
+ ReadState read_state() const { return read_state_; }
+
+ enum WriteState {
+ STATE_WRITABLE = 0, // we have received ping responses recently
+ STATE_WRITE_CONNECT = 1, // we have had a few ping failures
+ STATE_WRITE_TIMEOUT = 2 // we have had a large number of ping failures
+ };
+
+ WriteState write_state() const { return write_state_; }
+
+ // Determines whether the connection has finished connecting. This can only
+ // be false for TCP connections.
+ bool connected() const { return connected_; }
+
+ // Estimate of the round-trip time over this connection.
+ uint32 rtt() const { return rtt_; }
+
+ size_t sent_total_bytes();
+ size_t sent_bytes_second();
+ size_t recv_total_bytes();
+ size_t recv_bytes_second();
+ sigslot::signal1<Connection*> SignalStateChange;
+
+ // Sent when the connection has decided that it is no longer of value. It
+ // will delete itself immediately after this call.
+ sigslot::signal1<Connection*> SignalDestroyed;
+
+ // The connection can send and receive packets asynchronously. This matches
+ // the interface of AsyncPacketSocket, which may use UDP or TCP under the
+ // covers.
+ virtual int Send(const void* data, size_t size) = 0;
+
+ // Error if Send() returns < 0
+ virtual int GetError() = 0;
+
+ sigslot::signal3<Connection*, const char*, size_t> SignalReadPacket;
+
+ // Called when a packet is received on this connection.
+ void OnReadPacket(const char* data, size_t size);
+
+ // Called when a connection is determined to be no longer useful to us. We
+ // still keep it around in case the other side wants to use it. But we can
+ // safely stop pinging on it and we can allow it to time out if the other
+ // side stops using it as well.
+ bool pruned() const { return pruned_; }
+ void Prune();
+
+ // Makes the connection go away.
+ void Destroy();
+
+ // Checks that the state of this connection is up-to-date. The argument is
+ // the current time, which is compared against various timeouts.
+ void UpdateState(uint32 now);
+
+ // Called when this connection should try checking writability again.
+ uint32 last_ping_sent() const { return last_ping_sent_; }
+ void Ping(uint32 now);
+
+ // Called whenever a valid ping is received on this connection. This is
+ // public because the connection intercepts the first ping for us.
+ void ReceivedPing();
+
+ // Debugging description of this connection
+ std::string ToString() const;
+
+ bool reported() const { return reported_; }
+ void set_reported(bool reported) { reported_ = reported;}
+
+ protected:
+ // Constructs a new connection to the given remote port.
+ Connection(Port* port, size_t index, const Candidate& candidate);
+
+ // Called back when StunRequestManager has a stun packet to send
+ void OnSendStunPacket(const void* data, size_t size, StunRequest* req);
+
+ // Callbacks from ConnectionRequest
+ void OnConnectionRequestResponse(ConnectionRequest* req,
+ StunMessage* response);
+ void OnConnectionRequestErrorResponse(ConnectionRequest* req,
+ StunMessage* response);
+ void OnConnectionRequestTimeout(ConnectionRequest* req);
+
+ // Changes the state and signals if necessary.
+ void set_read_state(ReadState value);
+ void set_write_state(WriteState value);
+ void set_connected(bool value);
+
+ // Checks if this connection is useless, and hence, should be destroyed.
+ void CheckTimeout();
+
+ void OnMessage(talk_base::Message *pmsg);
+
+ Port* port_;
+ size_t local_candidate_index_;
+ Candidate remote_candidate_;
+ ReadState read_state_;
+ WriteState write_state_;
+ bool connected_;
+ bool pruned_;
+ StunRequestManager requests_;
+ uint32 rtt_;
+ uint32 last_ping_sent_; // last time we sent a ping to the other side
+ uint32 last_ping_received_; // last time we received a ping from the other
+ // side
+ uint32 last_data_received_;
+ std::vector<uint32> pings_since_last_response_;
+
+ talk_base::RateTracker recv_rate_tracker_;
+ talk_base::RateTracker send_rate_tracker_;
+
+ private:
+ bool reported_;
+
+ friend class Port;
+ friend class ConnectionRequest;
+};
+
+// ProxyConnection defers all the interesting work to the port
+class ProxyConnection : public Connection {
+ public:
+ ProxyConnection(Port* port, size_t index, const Candidate& candidate);
+
+ virtual int Send(const void* data, size_t size);
+ virtual int GetError() { return error_; }
+
+ private:
+ int error_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PORT_H_
diff --git a/talk/p2p/base/portallocator.h b/talk/p2p/base/portallocator.h
new file mode 100644
index 0000000..175bdbc
--- /dev/null
+++ b/talk/p2p/base/portallocator.h
@@ -0,0 +1,128 @@
+/*
+ * 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_P2P_BASE_PORTALLOCATOR_H_
+#define TALK_P2P_BASE_PORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+// PortAllocator is responsible for allocating Port types for a given
+// P2PSocket. It also handles port freeing.
+//
+// Clients can override this class to control port allocation, including
+// what kinds of ports are allocated.
+
+const uint32 PORTALLOCATOR_DISABLE_UDP = 0x01;
+const uint32 PORTALLOCATOR_DISABLE_STUN = 0x02;
+const uint32 PORTALLOCATOR_DISABLE_RELAY = 0x04;
+const uint32 PORTALLOCATOR_DISABLE_TCP = 0x08;
+const uint32 PORTALLOCATOR_ENABLE_SHAKER = 0x10;
+
+const uint32 kDefaultPortAllocatorFlags = 0;
+
+class PortAllocatorSession : public sigslot::has_slots<> {
+ public:
+ explicit PortAllocatorSession(uint32 flags) : flags_(flags) {}
+
+ // Subclasses should clean up any ports created.
+ virtual ~PortAllocatorSession() {}
+
+ uint32 flags() const { return flags_; }
+ void set_flags(uint32 flags) { flags_ = flags; }
+
+ // Prepares an initial set of ports to try.
+ virtual void GetInitialPorts() = 0;
+
+ // Starts and stops the flow of additional ports to try.
+ virtual void StartGetAllPorts() = 0;
+ virtual void StopGetAllPorts() = 0;
+ virtual bool IsGettingAllPorts() = 0;
+
+ sigslot::signal2<PortAllocatorSession*, Port*> SignalPortReady;
+ sigslot::signal2<PortAllocatorSession*,
+ const std::vector<Candidate>&> SignalCandidatesReady;
+
+ uint32 generation() { return generation_; }
+ void set_generation(uint32 generation) { generation_ = generation; }
+
+ private:
+ uint32 flags_;
+ uint32 generation_;
+};
+
+class PortAllocator {
+ public:
+ PortAllocator() :
+ flags_(kDefaultPortAllocatorFlags),
+ min_port_(0),
+ max_port_(0) {
+ }
+ virtual ~PortAllocator() {}
+
+ virtual PortAllocatorSession *CreateSession(const std::string &name,
+ const std::string &session_type) = 0;
+
+ uint32 flags() const { return flags_; }
+ void set_flags(uint32 flags) { flags_ = flags; }
+
+ const std::string& user_agent() const { return agent_; }
+ const talk_base::ProxyInfo& proxy() const { return proxy_; }
+ void set_proxy(const std::string& agent, const talk_base::ProxyInfo& proxy) {
+ agent_ = agent;
+ proxy_ = proxy;
+ }
+
+ // Gets/Sets the port range to use when choosing client ports.
+ int min_port() const { return min_port_; }
+ int max_port() const { return max_port_; }
+ bool SetPortRange(int min_port, int max_port) {
+ if (min_port > max_port) {
+ return false;
+ }
+
+ min_port_ = min_port;
+ max_port_ = max_port;
+ return true;
+ }
+
+ protected:
+ uint32 flags_;
+ std::string agent_;
+ talk_base::ProxyInfo proxy_;
+ int min_port_;
+ int max_port_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PORTALLOCATOR_H_
diff --git a/talk/p2p/base/pseudotcp.cc b/talk/p2p/base/pseudotcp.cc
new file mode 100644
index 0000000..c342dbc
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.cc
@@ -0,0 +1,1055 @@
+/*
+ * 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 "talk/p2p/base/pseudotcp.h"
+
+#include <cstdio>
+#include <cstdlib>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/socket.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/time.h"
+
+// The following logging is for detailed (packet-level) analysis only.
+#define _DBG_NONE 0
+#define _DBG_NORMAL 1
+#define _DBG_VERBOSE 2
+#define _DEBUGMSG _DBG_NONE
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// Network Constants
+//////////////////////////////////////////////////////////////////////
+
+// Standard MTUs
+const uint16 PACKET_MAXIMUMS[] = {
+ 65535, // Theoretical maximum, Hyperchannel
+ 32000, // Nothing
+ 17914, // 16Mb IBM Token Ring
+ 8166, // IEEE 802.4
+ //4464, // IEEE 802.5 (4Mb max)
+ 4352, // FDDI
+ //2048, // Wideband Network
+ 2002, // IEEE 802.5 (4Mb recommended)
+ //1536, // Expermental Ethernet Networks
+ //1500, // Ethernet, Point-to-Point (default)
+ 1492, // IEEE 802.3
+ 1006, // SLIP, ARPANET
+ //576, // X.25 Networks
+ //544, // DEC IP Portal
+ //512, // NETBIOS
+ 508, // IEEE 802/Source-Rt Bridge, ARCNET
+ 296, // Point-to-Point (low delay)
+ //68, // Official minimum
+ 0, // End of list marker
+};
+
+const uint32 MAX_PACKET = 65535;
+// Note: we removed lowest level because packet overhead was larger!
+const uint32 MIN_PACKET = 296;
+
+const uint32 IP_HEADER_SIZE = 20; // (+ up to 40 bytes of options?)
+const uint32 ICMP_HEADER_SIZE = 8;
+const uint32 UDP_HEADER_SIZE = 8;
+// TODO: Make JINGLE_HEADER_SIZE transparent to this code?
+const uint32 JINGLE_HEADER_SIZE = 64; // when relay framing is in use
+
+//////////////////////////////////////////////////////////////////////
+// Global Constants and Functions
+//////////////////////////////////////////////////////////////////////
+//
+// 0 1 2 3
+// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 0 | Conversation Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 4 | Sequence Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 8 | Acknowledgment Number |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// | | |U|A|P|R|S|F| |
+// 12 | Control | |R|C|S|S|Y|I| Window |
+// | | |G|K|H|T|N|N| |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 16 | Timestamp sending |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 20 | Timestamp receiving |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+// 24 | data |
+// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+//
+//////////////////////////////////////////////////////////////////////
+
+#define PSEUDO_KEEPALIVE 0
+
+const uint32 MAX_SEQ = 0xFFFFFFFF;
+const uint32 HEADER_SIZE = 24;
+const uint32 PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE;
+
+const uint32 MIN_RTO = 250; // 250 ms (RFC1122, Sec 4.2.3.1 "fractions of a second")
+const uint32 DEF_RTO = 3000; // 3 seconds (RFC1122, Sec 4.2.3.1)
+const uint32 MAX_RTO = 60000; // 60 seconds
+const uint32 ACK_DELAY = 100; // 100 milliseconds
+
+const uint8 FLAG_CTL = 0x02;
+const uint8 FLAG_RST = 0x04;
+
+const uint8 CTL_CONNECT = 0;
+//const uint8 CTL_REDIRECT = 1;
+const uint8 CTL_EXTRA = 255;
+
+/*
+const uint8 FLAG_FIN = 0x01;
+const uint8 FLAG_SYN = 0x02;
+const uint8 FLAG_ACK = 0x10;
+*/
+
+const uint32 CTRL_BOUND = 0x80000000;
+
+const long DEFAULT_TIMEOUT = 4000; // If there are no pending clocks, wake up every 4 seconds
+const long CLOSED_TIMEOUT = 60 * 1000; // If the connection is closed, once per minute
+
+#if PSEUDO_KEEPALIVE
+// !?! Rethink these times
+const uint32 IDLE_PING = 20 * 1000; // 20 seconds (note: WinXP SP2 firewall udp timeout is 90 seconds)
+const uint32 IDLE_TIMEOUT = 90 * 1000; // 90 seconds;
+#endif // PSEUDO_KEEPALIVE
+
+//////////////////////////////////////////////////////////////////////
+// Helper Functions
+//////////////////////////////////////////////////////////////////////
+
+inline void long_to_bytes(uint32 val, void* buf) {
+ *static_cast<uint32*>(buf) = talk_base::HostToNetwork32(val);
+}
+
+inline void short_to_bytes(uint16 val, void* buf) {
+ *static_cast<uint16*>(buf) = talk_base::HostToNetwork16(val);
+}
+
+inline uint32 bytes_to_long(const void* buf) {
+ return talk_base::NetworkToHost32(*static_cast<const uint32*>(buf));
+}
+
+inline uint16 bytes_to_short(const void* buf) {
+ return talk_base::NetworkToHost16(*static_cast<const uint16*>(buf));
+}
+
+uint32 bound(uint32 lower, uint32 middle, uint32 upper) {
+ return talk_base::_min(talk_base::_max(lower, middle), upper);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Debugging Statistics
+//////////////////////////////////////////////////////////////////////
+
+#if 0 // Not used yet
+
+enum Stat {
+ S_SENT_PACKET, // All packet sends
+ S_RESENT_PACKET, // All packet sends that are retransmits
+ S_RECV_PACKET, // All packet receives
+ S_RECV_NEW, // All packet receives that are too new
+ S_RECV_OLD, // All packet receives that are too old
+ S_NUM_STATS
+};
+
+const char* const STAT_NAMES[S_NUM_STATS] = {
+ "snt",
+ "snt-r",
+ "rcv"
+ "rcv-n",
+ "rcv-o"
+};
+
+int g_stats[S_NUM_STATS];
+inline void Incr(Stat s) { ++g_stats[s]; }
+void ReportStats() {
+ char buffer[256];
+ size_t len = 0;
+ for (int i = 0; i < S_NUM_STATS; ++i) {
+ len += talk_base::sprintfn(buffer, ARRAY_SIZE(buffer), "%s%s:%d",
+ (i == 0) ? "" : ",", STAT_NAMES[i], g_stats[i]);
+ g_stats[i] = 0;
+ }
+ LOG(LS_INFO) << "Stats[" << buffer << "]";
+}
+
+#endif
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+uint32 PseudoTcp::Now() {
+#if 0 // Use this to synchronize timers with logging timestamps (easier debug)
+ return talk_base::TimeSince(StartTime());
+#else
+ return talk_base::Time();
+#endif
+}
+
+PseudoTcp::PseudoTcp(IPseudoTcpNotify* notify, uint32 conv)
+ : m_notify(notify), m_shutdown(SD_NONE), m_error(0) {
+
+ // Sanity check on buffer sizes (needed for OnTcpWriteable notification logic)
+ ASSERT(sizeof(m_rbuf) + MIN_PACKET < sizeof(m_sbuf));
+
+ uint32 now = Now();
+
+ m_state = TCP_LISTEN;
+ m_conv = conv;
+ m_rcv_wnd = sizeof(m_rbuf);
+ m_snd_nxt = m_slen = 0;
+ m_snd_wnd = 1;
+ m_snd_una = m_rcv_nxt = m_rlen = 0;
+ m_bReadEnable = true;
+ m_bWriteEnable = false;
+ m_t_ack = 0;
+
+ m_msslevel = 0;
+ m_largest = 0;
+ ASSERT(MIN_PACKET > PACKET_OVERHEAD);
+ m_mss = MIN_PACKET - PACKET_OVERHEAD;
+ m_mtu_advise = MAX_PACKET;
+
+ m_rto_base = 0;
+
+ m_cwnd = 2 * m_mss;
+ m_ssthresh = sizeof(m_rbuf);
+ m_lastrecv = m_lastsend = m_lasttraffic = now;
+ m_bOutgoing = false;
+
+ m_dup_acks = 0;
+ m_recover = 0;
+
+ m_ts_recent = m_ts_lastack = 0;
+
+ m_rx_rto = DEF_RTO;
+ m_rx_srtt = m_rx_rttvar = 0;
+}
+
+PseudoTcp::~PseudoTcp() {
+}
+
+int PseudoTcp::Connect() {
+ if (m_state != TCP_LISTEN) {
+ m_error = EINVAL;
+ return -1;
+ }
+
+ m_state = TCP_SYN_SENT;
+ LOG(LS_INFO) << "State: TCP_SYN_SENT";
+
+ char buffer[1];
+ buffer[0] = CTL_CONNECT;
+ queue(buffer, 1, true);
+ attemptSend();
+
+ return 0;
+}
+
+void PseudoTcp::NotifyMTU(uint16 mtu) {
+ m_mtu_advise = mtu;
+ if (m_state == TCP_ESTABLISHED) {
+ adjustMTU();
+ }
+}
+
+void PseudoTcp::NotifyClock(uint32 now) {
+ if (m_state == TCP_CLOSED)
+ return;
+
+ // Check if it's time to retransmit a segment
+ if (m_rto_base && (talk_base::TimeDiff(m_rto_base + m_rx_rto, now) <= 0)) {
+ if (m_slist.empty()) {
+ ASSERT(false);
+ } else {
+ // Note: (m_slist.front().xmit == 0)) {
+ // retransmit segments
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "timeout retransmit (rto: " << m_rx_rto
+ << ") (rto_base: " << m_rto_base
+ << ") (now: " << now
+ << ") (dup_acks: " << static_cast<unsigned>(m_dup_acks)
+ << ")";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+ //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_mss;
+
+ // Back off retransmit timer. Note: the limit is lower when connecting.
+ uint32 rto_limit = (m_state < TCP_ESTABLISHED) ? DEF_RTO : MAX_RTO;
+ m_rx_rto = talk_base::_min(rto_limit, m_rx_rto * 2);
+ m_rto_base = now;
+ }
+ }
+
+ // Check if it's time to probe closed windows
+ if ((m_snd_wnd == 0)
+ && (talk_base::TimeDiff(m_lastsend + m_rx_rto, now) <= 0)) {
+ if (talk_base::TimeDiff(now, m_lastrecv) >= 15000) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // probe the window
+ packet(m_snd_nxt - 1, 0, 0, 0);
+ m_lastsend = now;
+
+ // back off retransmit timer
+ m_rx_rto = talk_base::_min(MAX_RTO, m_rx_rto * 2);
+ }
+
+ // Check if it's time to send delayed acks
+ if (m_t_ack && (talk_base::TimeDiff(m_t_ack + ACK_DELAY, now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+
+#if PSEUDO_KEEPALIVE
+ // Check for idle timeout
+ if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lastrecv + IDLE_TIMEOUT, now) <= 0)) {
+ closedown(ECONNABORTED);
+ return;
+ }
+
+ // Check for ping timeout (to keep udp mapping open)
+ if ((m_state == TCP_ESTABLISHED) && (TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now) <= 0)) {
+ packet(m_snd_nxt, 0, 0, 0);
+ }
+#endif // PSEUDO_KEEPALIVE
+}
+
+bool PseudoTcp::NotifyPacket(const char* buffer, size_t len) {
+ if (len > MAX_PACKET) {
+ LOG_F(WARNING) << "packet too large";
+ return false;
+ }
+ return parse(reinterpret_cast<const uint8 *>(buffer), uint32(len));
+}
+
+bool PseudoTcp::GetNextClock(uint32 now, long& timeout) {
+ return clock_check(now, timeout);
+}
+
+//
+// IPStream Implementation
+//
+
+int PseudoTcp::Recv(char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ if (m_rlen == 0) {
+ m_bReadEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ uint32 read = talk_base::_min(uint32(len), m_rlen);
+ memcpy(buffer, m_rbuf, read);
+ m_rlen -= read;
+
+ // !?! until we create a circular buffer, we need to move all of the rest of the buffer up!
+ memmove(m_rbuf, m_rbuf + read, sizeof(m_rbuf) - read/*m_rlen*/);
+
+ if ((sizeof(m_rbuf) - m_rlen - m_rcv_wnd)
+ >= talk_base::_min<uint32>(sizeof(m_rbuf) / 2, m_mss)) {
+ bool bWasClosed = (m_rcv_wnd == 0); // !?! Not sure about this was closed business
+
+ m_rcv_wnd = sizeof(m_rbuf) - m_rlen;
+
+ if (bWasClosed) {
+ attemptSend(sfImmediateAck);
+ }
+ }
+
+ return read;
+}
+
+int PseudoTcp::Send(const char* buffer, size_t len) {
+ if (m_state != TCP_ESTABLISHED) {
+ m_error = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ if (m_slen == sizeof(m_sbuf)) {
+ m_bWriteEnable = true;
+ m_error = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+
+ int written = queue(buffer, uint32(len), false);
+ attemptSend();
+ return written;
+}
+
+void PseudoTcp::Close(bool force) {
+ LOG_F(LS_VERBOSE) << "(" << (force ? "true" : "false") << ")";
+ m_shutdown = force ? SD_FORCEFUL : SD_GRACEFUL;
+}
+
+int PseudoTcp::GetError() {
+ return m_error;
+}
+
+//
+// Internal Implementation
+//
+
+uint32 PseudoTcp::queue(const char* data, uint32 len, bool bCtrl) {
+ if (len > sizeof(m_sbuf) - m_slen) {
+ ASSERT(!bCtrl);
+ len = sizeof(m_sbuf) - m_slen;
+ }
+
+ // We can concatenate data if the last segment is the same type
+ // (control v. regular data), and has not been transmitted yet
+ if (!m_slist.empty() && (m_slist.back().bCtrl == bCtrl) && (m_slist.back().xmit == 0)) {
+ m_slist.back().len += len;
+ } else {
+ SSegment sseg(m_snd_una + m_slen, len, bCtrl);
+ m_slist.push_back(sseg);
+ }
+
+ memcpy(m_sbuf + m_slen, data, len);
+ m_slen += len;
+ //LOG(LS_INFO) << "PseudoTcp::queue - m_slen = " << m_slen;
+ return len;
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcp::packet(uint32 seq, uint8 flags,
+ const char* data, uint32 len) {
+ ASSERT(HEADER_SIZE + len <= MAX_PACKET);
+
+ uint32 now = Now();
+
+ uint8 buffer[MAX_PACKET];
+ long_to_bytes(m_conv, buffer);
+ long_to_bytes(seq, buffer + 4);
+ long_to_bytes(m_rcv_nxt, buffer + 8);
+ buffer[12] = 0;
+ buffer[13] = flags;
+ short_to_bytes(uint16(m_rcv_wnd), buffer + 14);
+
+ // Timestamp computations
+ long_to_bytes(now, buffer + 16);
+ long_to_bytes(m_ts_recent, buffer + 20);
+ m_ts_lastack = m_rcv_nxt;
+
+ memcpy(buffer + HEADER_SIZE, data, len);
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "<-- <CONV=" << m_conv
+ << "><FLG=" << static_cast<unsigned>(flags)
+ << "><SEQ=" << seq << ":" << seq + len
+ << "><ACK=" << m_rcv_nxt
+ << "><WND=" << m_rcv_wnd
+ << "><TS=" << (now % 10000)
+ << "><TSR=" << (m_ts_recent % 10000)
+ << "><LEN=" << len << ">";
+#endif // _DEBUGMSG
+
+ IPseudoTcpNotify::WriteResult wres = m_notify->TcpWritePacket(this, reinterpret_cast<char *>(buffer), len + HEADER_SIZE);
+ // Note: When data is NULL, this is an ACK packet. We don't read the return value for those,
+ // and thus we won't retry. So go ahead and treat the packet as a success (basically simulate
+ // as if it were dropped), which will prevent our timers from being messed up.
+ if ((wres != IPseudoTcpNotify::WR_SUCCESS) && (NULL != data))
+ return wres;
+
+ m_t_ack = 0;
+ if (len > 0) {
+ m_lastsend = now;
+ }
+ m_lasttraffic = now;
+ m_bOutgoing = true;
+
+ return IPseudoTcpNotify::WR_SUCCESS;
+}
+
+bool PseudoTcp::parse(const uint8* buffer, uint32 size) {
+ if (size < 12)
+ return false;
+
+ Segment seg;
+ seg.conv = bytes_to_long(buffer);
+ seg.seq = bytes_to_long(buffer + 4);
+ seg.ack = bytes_to_long(buffer + 8);
+ seg.flags = buffer[13];
+ seg.wnd = bytes_to_short(buffer + 14);
+
+ seg.tsval = bytes_to_long(buffer + 16);
+ seg.tsecr = bytes_to_long(buffer + 20);
+
+ seg.data = reinterpret_cast<const char *>(buffer) + HEADER_SIZE;
+ seg.len = size - HEADER_SIZE;
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "--> <CONV=" << seg.conv
+ << "><FLG=" << static_cast<unsigned>(seg.flags)
+ << "><SEQ=" << seg.seq << ":" << seg.seq + seg.len
+ << "><ACK=" << seg.ack
+ << "><WND=" << seg.wnd
+ << "><TS=" << (seg.tsval % 10000)
+ << "><TSR=" << (seg.tsecr % 10000)
+ << "><LEN=" << seg.len << ">";
+#endif // _DEBUGMSG
+
+ return process(seg);
+}
+
+bool PseudoTcp::clock_check(uint32 now, long& nTimeout) {
+ if (m_shutdown == SD_FORCEFUL)
+ return false;
+
+ if ((m_shutdown == SD_GRACEFUL)
+ && ((m_state != TCP_ESTABLISHED)
+ || ((m_slen == 0) && (m_t_ack == 0)))) {
+ return false;
+ }
+
+ if (m_state == TCP_CLOSED) {
+ nTimeout = CLOSED_TIMEOUT;
+ return true;
+ }
+
+ nTimeout = DEFAULT_TIMEOUT;
+
+ if (m_t_ack) {
+ nTimeout = talk_base::_min<int32>(nTimeout,
+ talk_base::TimeDiff(m_t_ack + ACK_DELAY, now));
+ }
+ if (m_rto_base) {
+ nTimeout = talk_base::_min<int32>(nTimeout,
+ talk_base::TimeDiff(m_rto_base + m_rx_rto, now));
+ }
+ if (m_snd_wnd == 0) {
+ nTimeout = talk_base::_min<int32>(nTimeout, talk_base::TimeDiff(m_lastsend + m_rx_rto, now));
+ }
+#if PSEUDO_KEEPALIVE
+ if (m_state == TCP_ESTABLISHED) {
+ nTimeout = talk_base::_min<int32>(nTimeout,
+ talk_base::TimeDiff(m_lasttraffic + (m_bOutgoing ? IDLE_PING * 3/2 : IDLE_PING), now));
+ }
+#endif // PSEUDO_KEEPALIVE
+ return true;
+}
+
+bool PseudoTcp::process(Segment& seg) {
+ // If this is the wrong conversation, send a reset!?! (with the correct conversation?)
+ if (seg.conv != m_conv) {
+ //if ((seg.flags & FLAG_RST) == 0) {
+ // packet(tcb, seg.ack, 0, FLAG_RST, 0, 0);
+ //}
+ LOG_F(LS_ERROR) << "wrong conversation";
+ return false;
+ }
+
+ uint32 now = Now();
+ m_lasttraffic = m_lastrecv = now;
+ m_bOutgoing = false;
+
+ if (m_state == TCP_CLOSED) {
+ // !?! send reset?
+ LOG_F(LS_ERROR) << "closed";
+ return false;
+ }
+
+ // Check if this is a reset segment
+ if (seg.flags & FLAG_RST) {
+ closedown(ECONNRESET);
+ return false;
+ }
+
+ // Check for control data
+ bool bConnect = false;
+ if (seg.flags & FLAG_CTL) {
+ if (seg.len == 0) {
+ LOG_F(LS_ERROR) << "Missing control code";
+ return false;
+ } else if (seg.data[0] == CTL_CONNECT) {
+ bConnect = true;
+ if (m_state == TCP_LISTEN) {
+ m_state = TCP_SYN_RECEIVED;
+ LOG(LS_INFO) << "State: TCP_SYN_RECEIVED";
+ //m_notify->associate(addr);
+ char buffer[1];
+ buffer[0] = CTL_CONNECT;
+ queue(buffer, 1, true);
+ } else if (m_state == TCP_SYN_SENT) {
+ m_state = TCP_ESTABLISHED;
+ LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ //notify(evOpen);
+ }
+ } else {
+ LOG_F(LS_WARNING) << "Unknown control code: " << seg.data[0];
+ return false;
+ }
+ }
+
+ // Update timestamp
+ if ((seg.seq <= m_ts_lastack) && (m_ts_lastack < seg.seq + seg.len)) {
+ m_ts_recent = seg.tsval;
+ }
+
+ // Check if this is a valuable ack
+ if ((seg.ack > m_snd_una) && (seg.ack <= m_snd_nxt)) {
+ // Calculate round-trip time
+ if (seg.tsecr) {
+ long rtt = talk_base::TimeDiff(now, seg.tsecr);
+ if (rtt >= 0) {
+ if (m_rx_srtt == 0) {
+ m_rx_srtt = rtt;
+ m_rx_rttvar = rtt / 2;
+ } else {
+ m_rx_rttvar = (3 * m_rx_rttvar + abs(long(rtt - m_rx_srtt))) / 4;
+ m_rx_srtt = (7 * m_rx_srtt + rtt) / 8;
+ }
+ m_rx_rto = bound(MIN_RTO, m_rx_srtt +
+ talk_base::_max<uint32>(1, 4 * m_rx_rttvar), MAX_RTO);
+#if _DEBUGMSG >= _DBG_VERBOSE
+ LOG(LS_INFO) << "rtt: " << rtt
+ << " srtt: " << m_rx_srtt
+ << " rto: " << m_rx_rto;
+#endif // _DEBUGMSG
+ } else {
+ ASSERT(false);
+ }
+ }
+
+ m_snd_wnd = seg.wnd;
+
+ uint32 nAcked = seg.ack - m_snd_una;
+ m_snd_una = seg.ack;
+
+ m_rto_base = (m_snd_una == m_snd_nxt) ? 0 : now;
+
+ m_slen -= nAcked;
+ memmove(m_sbuf, m_sbuf + nAcked, m_slen);
+ //LOG(LS_INFO) << "PseudoTcp::process - m_slen = " << m_slen;
+
+ for (uint32 nFree = nAcked; nFree > 0; ) {
+ ASSERT(!m_slist.empty());
+ if (nFree < m_slist.front().len) {
+ m_slist.front().len -= nFree;
+ nFree = 0;
+ } else {
+ if (m_slist.front().len > m_largest) {
+ m_largest = m_slist.front().len;
+ }
+ nFree -= m_slist.front().len;
+ m_slist.pop_front();
+ }
+ }
+
+ if (m_dup_acks >= 3) {
+ if (m_snd_una >= m_recover) { // NewReno
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_cwnd = talk_base::_min(m_ssthresh, nInFlight + m_mss); // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "exit recovery";
+#endif // _DEBUGMSG
+ m_dup_acks = 0;
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_cwnd += m_mss - talk_base::_min(nAcked, m_cwnd);
+ }
+ } else {
+ m_dup_acks = 0;
+ // Slow start, congestion avoidance
+ if (m_cwnd < m_ssthresh) {
+ m_cwnd += m_mss;
+ } else {
+ m_cwnd += talk_base::_max<uint32>(1, m_mss * m_mss / m_cwnd);
+ }
+ }
+
+ // !?! A bit hacky
+ if ((m_state == TCP_SYN_RECEIVED) && !bConnect) {
+ m_state = TCP_ESTABLISHED;
+ LOG(LS_INFO) << "State: TCP_ESTABLISHED";
+ adjustMTU();
+ if (m_notify) {
+ m_notify->OnTcpOpen(this);
+ }
+ //notify(evOpen);
+ }
+
+ // If we make room in the send queue, notify the user
+ // The goal it to make sure we always have at least enough data to fill the
+ // window. We'd like to notify the app when we are halfway to that point.
+ const uint32 kIdealRefillSize = (sizeof(m_sbuf) + sizeof(m_rbuf)) / 2;
+ if (m_bWriteEnable && (m_slen < kIdealRefillSize)) {
+ m_bWriteEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpWriteable(this);
+ }
+ //notify(evWrite);
+ }
+ } else if (seg.ack == m_snd_una) {
+ // !?! Note, tcp says don't do this... but otherwise how does a closed window become open?
+ m_snd_wnd = seg.wnd;
+
+ // Check duplicate acks
+ if (seg.len > 0) {
+ // it's a dup ack, but with a data payload, so don't modify m_dup_acks
+ } else if (m_snd_una != m_snd_nxt) {
+ m_dup_acks += 1;
+ if (m_dup_acks == 3) { // (Fast Retransmit)
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "enter recovery";
+ LOG(LS_INFO) << "recovery retransmit";
+#endif // _DEBUGMSG
+ if (!transmit(m_slist.begin(), now)) {
+ closedown(ECONNABORTED);
+ return false;
+ }
+ m_recover = m_snd_nxt;
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ m_ssthresh = talk_base::_max(nInFlight / 2, 2 * m_mss);
+ //LOG(LS_INFO) << "m_ssthresh: " << m_ssthresh << " nInFlight: " << nInFlight << " m_mss: " << m_mss;
+ m_cwnd = m_ssthresh + 3 * m_mss;
+ } else if (m_dup_acks > 3) {
+ m_cwnd += m_mss;
+ }
+ } else {
+ m_dup_acks = 0;
+ }
+ }
+
+ // Conditions were acks must be sent:
+ // 1) Segment is too old (they missed an ACK) (immediately)
+ // 2) Segment is too new (we missed a segment) (immediately)
+ // 3) Segment has data (so we need to ACK!) (delayed)
+ // ... so the only time we don't need to ACK, is an empty segment that points to rcv_nxt!
+
+ SendFlags sflags = sfNone;
+ if (seg.seq != m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ } else if (seg.len != 0) {
+ sflags = sfDelayedAck;
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ if (sflags == sfImmediateAck) {
+ if (seg.seq > m_rcv_nxt) {
+ LOG_F(LS_INFO) << "too new";
+ } else if (seg.seq + seg.len <= m_rcv_nxt) {
+ LOG_F(LS_INFO) << "too old";
+ }
+ }
+#endif // _DEBUGMSG
+
+ // Adjust the incoming segment to fit our receive buffer
+ if (seg.seq < m_rcv_nxt) {
+ uint32 nAdjust = m_rcv_nxt - seg.seq;
+ if (nAdjust < seg.len) {
+ seg.seq += nAdjust;
+ seg.data += nAdjust;
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+ if ((seg.seq + seg.len - m_rcv_nxt) > (sizeof(m_rbuf) - m_rlen)) {
+ uint32 nAdjust = seg.seq + seg.len - m_rcv_nxt - (sizeof(m_rbuf) - m_rlen);
+ if (nAdjust < seg.len) {
+ seg.len -= nAdjust;
+ } else {
+ seg.len = 0;
+ }
+ }
+
+ bool bIgnoreData = (seg.flags & FLAG_CTL) || (m_shutdown != SD_NONE);
+ bool bNewData = false;
+
+ if (seg.len > 0) {
+ if (bIgnoreData) {
+ if (seg.seq == m_rcv_nxt) {
+ m_rcv_nxt += seg.len;
+ }
+ } else {
+ uint32 nOffset = seg.seq - m_rcv_nxt;
+ memcpy(m_rbuf + m_rlen + nOffset, seg.data, seg.len);
+ if (seg.seq == m_rcv_nxt) {
+ m_rlen += seg.len;
+ m_rcv_nxt += seg.len;
+ m_rcv_wnd -= seg.len;
+ bNewData = true;
+
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq <= m_rcv_nxt)) {
+ if (it->seq + it->len > m_rcv_nxt) {
+ sflags = sfImmediateAck; // (Fast Recovery)
+ uint32 nAdjust = (it->seq + it->len) - m_rcv_nxt;
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Recovered " << nAdjust << " bytes (" << m_rcv_nxt << " -> " << m_rcv_nxt + nAdjust << ")";
+#endif // _DEBUGMSG
+ m_rlen += nAdjust;
+ m_rcv_nxt += nAdjust;
+ m_rcv_wnd -= nAdjust;
+ }
+ it = m_rlist.erase(it);
+ }
+ } else {
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Saving " << seg.len << " bytes (" << seg.seq << " -> " << seg.seq + seg.len << ")";
+#endif // _DEBUGMSG
+ RSegment rseg;
+ rseg.seq = seg.seq;
+ rseg.len = seg.len;
+ RList::iterator it = m_rlist.begin();
+ while ((it != m_rlist.end()) && (it->seq < rseg.seq)) {
+ ++it;
+ }
+ m_rlist.insert(it, rseg);
+ }
+ }
+ }
+
+ attemptSend(sflags);
+
+ // If we have new data, notify the user
+ if (bNewData && m_bReadEnable) {
+ m_bReadEnable = false;
+ if (m_notify) {
+ m_notify->OnTcpReadable(this);
+ }
+ //notify(evRead);
+ }
+
+ return true;
+}
+
+bool PseudoTcp::transmit(const SList::iterator& seg, uint32 now) {
+ if (seg->xmit >= ((m_state == TCP_ESTABLISHED) ? 15 : 30)) {
+ LOG_F(LS_VERBOSE) << "too many retransmits";
+ return false;
+ }
+
+ uint32 nTransmit = talk_base::_min(seg->len, m_mss);
+
+ while (true) {
+ uint32 seq = seg->seq;
+ uint8 flags = (seg->bCtrl ? FLAG_CTL : 0);
+ const char* buffer = m_sbuf + (seg->seq - m_snd_una);
+ IPseudoTcpNotify::WriteResult wres = this->packet(seq, flags, buffer, nTransmit);
+
+ if (wres == IPseudoTcpNotify::WR_SUCCESS)
+ break;
+
+ if (wres == IPseudoTcpNotify::WR_FAIL) {
+ LOG_F(LS_VERBOSE) << "packet failed";
+ return false;
+ }
+
+ ASSERT(wres == IPseudoTcpNotify::WR_TOO_LARGE);
+
+ while (true) {
+ if (PACKET_MAXIMUMS[m_msslevel + 1] == 0) {
+ LOG_F(LS_VERBOSE) << "MTU too small";
+ return false;
+ }
+ // !?! We need to break up all outstanding and pending packets and then retransmit!?!
+
+ m_mss = PACKET_MAXIMUMS[++m_msslevel] - PACKET_OVERHEAD;
+ m_cwnd = 2 * m_mss; // I added this... haven't researched actual formula
+ if (m_mss < nTransmit) {
+ nTransmit = m_mss;
+ break;
+ }
+ }
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ }
+
+ if (nTransmit < seg->len) {
+ LOG_F(LS_VERBOSE) << "mss reduced to " << m_mss;
+
+ SSegment subseg(seg->seq + nTransmit, seg->len - nTransmit, seg->bCtrl);
+ //subseg.tstamp = seg->tstamp;
+ subseg.xmit = seg->xmit;
+ seg->len = nTransmit;
+
+ SList::iterator next = seg;
+ m_slist.insert(++next, subseg);
+ }
+
+ if (seg->xmit == 0) {
+ m_snd_nxt += seg->len;
+ }
+ seg->xmit += 1;
+ //seg->tstamp = now;
+ if (m_rto_base == 0) {
+ m_rto_base = now;
+ }
+
+ return true;
+}
+
+void PseudoTcp::attemptSend(SendFlags sflags) {
+ uint32 now = Now();
+
+ if (talk_base::TimeDiff(now, m_lastsend) > static_cast<long>(m_rx_rto)) {
+ m_cwnd = m_mss;
+ }
+
+#if _DEBUGMSG
+ bool bFirst = true;
+ UNUSED(bFirst);
+#endif // _DEBUGMSG
+
+ while (true) {
+ uint32 cwnd = m_cwnd;
+ if ((m_dup_acks == 1) || (m_dup_acks == 2)) { // Limited Transmit
+ cwnd += m_dup_acks * m_mss;
+ }
+ uint32 nWindow = talk_base::_min(m_snd_wnd, cwnd);
+ uint32 nInFlight = m_snd_nxt - m_snd_una;
+ uint32 nUseable = (nInFlight < nWindow) ? (nWindow - nInFlight) : 0;
+
+ uint32 nAvailable = talk_base::_min(m_slen - nInFlight, m_mss);
+
+ if (nAvailable > nUseable) {
+ if (nUseable * 4 < nWindow) {
+ // RFC 813 - avoid SWS
+ nAvailable = 0;
+ } else {
+ nAvailable = nUseable;
+ }
+ }
+
+#if _DEBUGMSG >= _DBG_VERBOSE
+ if (bFirst) {
+ bFirst = false;
+ LOG(LS_INFO) << "[cwnd: " << m_cwnd
+ << " nWindow: " << nWindow
+ << " nInFlight: " << nInFlight
+ << " nAvailable: " << nAvailable
+ << " nQueued: " << m_slen - nInFlight
+ << " nEmpty: " << sizeof(m_sbuf) - m_slen
+ << " ssthresh: " << m_ssthresh << "]";
+ }
+#endif // _DEBUGMSG
+
+ if (nAvailable == 0) {
+ if (sflags == sfNone)
+ return;
+
+ // If this is an immediate ack, or the second delayed ack
+ if ((sflags == sfImmediateAck) || m_t_ack) {
+ packet(m_snd_nxt, 0, 0, 0);
+ } else {
+ m_t_ack = Now();
+ }
+ return;
+ }
+
+ // Nagle algorithm
+ if ((m_snd_nxt > m_snd_una) && (nAvailable < m_mss)) {
+ return;
+ }
+
+ // Find the next segment to transmit
+ SList::iterator it = m_slist.begin();
+ while (it->xmit > 0) {
+ ++it;
+ ASSERT(it != m_slist.end());
+ }
+ SList::iterator seg = it;
+
+ // If the segment is too large, break it into two
+ if (seg->len > nAvailable) {
+ SSegment subseg(seg->seq + nAvailable, seg->len - nAvailable, seg->bCtrl);
+ seg->len = nAvailable;
+ m_slist.insert(++it, subseg);
+ }
+
+ if (!transmit(seg, now)) {
+ LOG_F(LS_VERBOSE) << "transmit failed";
+ // TODO: consider closing socket
+ return;
+ }
+
+ sflags = sfNone;
+ }
+}
+
+void
+PseudoTcp::closedown(uint32 err) {
+ m_slen = 0;
+
+ LOG(LS_INFO) << "State: TCP_CLOSED";
+ m_state = TCP_CLOSED;
+ if (m_notify) {
+ m_notify->OnTcpClosed(this, err);
+ }
+ //notify(evClose, err);
+}
+
+void
+PseudoTcp::adjustMTU() {
+ // Determine our current mss level, so that we can adjust appropriately later
+ for (m_msslevel = 0; PACKET_MAXIMUMS[m_msslevel + 1] > 0; ++m_msslevel) {
+ if (static_cast<uint16>(PACKET_MAXIMUMS[m_msslevel]) <= m_mtu_advise) {
+ break;
+ }
+ }
+ m_mss = m_mtu_advise - PACKET_OVERHEAD;
+ // !?! Should we reset m_largest here?
+#if _DEBUGMSG >= _DBG_NORMAL
+ LOG(LS_INFO) << "Adjusting mss to " << m_mss << " bytes";
+#endif // _DEBUGMSG
+ // Enforce minimums on ssthresh and cwnd
+ m_ssthresh = talk_base::_max(m_ssthresh, 2 * m_mss);
+ m_cwnd = talk_base::_max(m_cwnd, m_mss);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/pseudotcp.h b/talk/p2p/base/pseudotcp.h
new file mode 100644
index 0000000..1446201
--- /dev/null
+++ b/talk/p2p/base/pseudotcp.h
@@ -0,0 +1,187 @@
+/*
+ * 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_P2P_BASE_PSEUDOTCP_H_
+#define TALK_P2P_BASE_PSEUDOTCP_H_
+
+#include <list>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////
+// IPseudoTcpNotify
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp;
+
+class IPseudoTcpNotify {
+ public:
+ virtual ~IPseudoTcpNotify() {}
+ // Notification of tcp events
+ virtual void OnTcpOpen(PseudoTcp* tcp) = 0;
+ virtual void OnTcpReadable(PseudoTcp* tcp) = 0;
+ virtual void OnTcpWriteable(PseudoTcp* tcp) = 0;
+ virtual void OnTcpClosed(PseudoTcp* tcp, uint32 error) = 0;
+
+ // Write the packet onto the network
+ enum WriteResult { WR_SUCCESS, WR_TOO_LARGE, WR_FAIL };
+ virtual WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer, size_t len) = 0;
+};
+
+//////////////////////////////////////////////////////////////////////
+// PseudoTcp
+//////////////////////////////////////////////////////////////////////
+
+class PseudoTcp {
+ public:
+ static uint32 Now();
+
+ PseudoTcp(IPseudoTcpNotify* notify, uint32 conv);
+ virtual ~PseudoTcp();
+
+ int Connect();
+ int Recv(char* buffer, size_t len);
+ int Send(const char* buffer, size_t len);
+ void Close(bool force);
+ int GetError();
+
+ enum TcpState {
+ TCP_LISTEN, TCP_SYN_SENT, TCP_SYN_RECEIVED, TCP_ESTABLISHED, TCP_CLOSED
+ };
+ TcpState State() const { return m_state; }
+
+ // Call this when the PMTU changes.
+ void NotifyMTU(uint16 mtu);
+
+ // Call this based on timeout value returned from GetNextClock.
+ // It's ok to call this too frequently.
+ void NotifyClock(uint32 now);
+
+ // Call this whenever a packet arrives.
+ // Returns true if the packet was processed successfully.
+ bool NotifyPacket(const char * buffer, size_t len);
+
+ // Call this to determine the next time NotifyClock should be called.
+ // Returns false if the socket is ready to be destroyed.
+ bool GetNextClock(uint32 now, long& timeout);
+
+ protected:
+ enum SendFlags { sfNone, sfDelayedAck, sfImmediateAck };
+ enum {
+ // Note: can't go as high as 1024 * 64, because of uint16 precision
+ kRcvBufSize = 1024 * 60,
+ // Note: send buffer should be larger to make sure we can always fill the
+ // receiver window
+ kSndBufSize = 1024 * 90
+ };
+
+ struct Segment {
+ uint32 conv, seq, ack;
+ uint8 flags;
+ uint16 wnd;
+ const char * data;
+ uint32 len;
+ uint32 tsval, tsecr;
+ };
+
+ struct SSegment {
+ SSegment(uint32 s, uint32 l, bool c)
+ : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) {
+ }
+ uint32 seq, len;
+ //uint32 tstamp;
+ uint8 xmit;
+ bool bCtrl;
+ };
+ typedef std::list<SSegment> SList;
+
+ struct RSegment {
+ uint32 seq, len;
+ };
+
+ uint32 queue(const char* data, uint32 len, bool bCtrl);
+
+ IPseudoTcpNotify::WriteResult packet(uint32 seq, uint8 flags,
+ const char* data, uint32 len);
+ bool parse(const uint8* buffer, uint32 size);
+
+ void attemptSend(SendFlags sflags = sfNone);
+
+ void closedown(uint32 err = 0);
+
+ bool clock_check(uint32 now, long& nTimeout);
+
+ bool process(Segment& seg);
+ bool transmit(const SList::iterator& seg, uint32 now);
+
+ void adjustMTU();
+
+ private:
+ IPseudoTcpNotify* m_notify;
+ enum Shutdown { SD_NONE, SD_GRACEFUL, SD_FORCEFUL } m_shutdown;
+ int m_error;
+
+ // TCB data
+ TcpState m_state;
+ uint32 m_conv;
+ bool m_bReadEnable, m_bWriteEnable, m_bOutgoing;
+ uint32 m_lasttraffic;
+
+ // Incoming data
+ typedef std::list<RSegment> RList;
+ RList m_rlist;
+ char m_rbuf[kRcvBufSize];
+ uint32 m_rcv_nxt, m_rcv_wnd, m_rlen, m_lastrecv;
+
+ // Outgoing data
+ SList m_slist;
+ char m_sbuf[kSndBufSize];
+ uint32 m_snd_nxt, m_snd_wnd, m_slen, m_lastsend, m_snd_una;
+ // Maximum segment size, estimated protocol level, largest segment sent
+ uint32 m_mss, m_msslevel, m_largest, m_mtu_advise;
+ // Retransmit timer
+ uint32 m_rto_base;
+
+ // Timestamp tracking
+ uint32 m_ts_recent, m_ts_lastack;
+
+ // Round-trip calculation
+ uint32 m_rx_rttvar, m_rx_srtt, m_rx_rto;
+
+ // Congestion avoidance, Fast retransmit/recovery, Delayed ACKs
+ uint32 m_ssthresh, m_cwnd;
+ uint8 m_dup_acks;
+ uint32 m_recover;
+ uint32 m_t_ack;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_PSEUDOTCP_H_
diff --git a/talk/p2p/base/rawtransport.cc b/talk/p2p/base/rawtransport.cc
new file mode 100644
index 0000000..b07d69c
--- /dev/null
+++ b/talk/p2p/base/rawtransport.cc
@@ -0,0 +1,129 @@
+/*
+ * 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 <string>
+#include <vector>
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/rawtransportchannel.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+RawTransport::RawTransport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ PortAllocator* allocator)
+ : Transport(signaling_thread, worker_thread,
+ NS_GINGLE_RAW, allocator) {
+}
+
+RawTransport::~RawTransport() {
+ DestroyAllChannels();
+}
+
+bool RawTransport::ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ Candidates* candidates,
+ ParseError* error) {
+ ASSERT(elem->FirstChild() == NULL);
+ for (const buzz::XmlElement* cand_elem = elem->FirstElement();
+ cand_elem != NULL;
+ cand_elem = cand_elem->NextElement()) {
+ if (cand_elem->Name() == QN_GINGLE_RAW_CHANNEL) {
+ talk_base::SocketAddress addr;
+ if (!ParseRawAddress(cand_elem, &addr, error))
+ return false;
+
+ Candidate candidate;
+ candidate.set_name(cand_elem->Attr(buzz::QN_NAME));
+ candidate.set_address(addr);
+ candidates->push_back(candidate);
+ }
+ }
+ return true;
+}
+
+bool RawTransport::WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ XmlElements* candidate_elems,
+ WriteError* error) {
+ for (std::vector<Candidate>::const_iterator
+ cand = candidates.begin();
+ cand != candidates.end();
+ ++cand) {
+ ASSERT(cand->protocol() == "udp");
+ talk_base::SocketAddress addr = cand->address();
+
+ buzz::XmlElement* elem = new buzz::XmlElement(QN_GINGLE_RAW_CHANNEL);
+ elem->SetAttr(buzz::QN_NAME, type());
+ elem->SetAttr(QN_ADDRESS, addr.IPAsString());
+ elem->SetAttr(QN_PORT, addr.PortAsString());
+ candidate_elems->push_back(elem);
+ }
+ return true;
+}
+
+bool RawTransport::ParseRawAddress(const buzz::XmlElement* elem,
+ talk_base::SocketAddress* addr,
+ ParseError* error) {
+ // Make sure the required attributes exist
+ if (!elem->HasAttr(buzz::QN_NAME) ||
+ !elem->HasAttr(QN_ADDRESS) ||
+ !elem->HasAttr(QN_PORT)) {
+ return BadParse("channel missing required attribute", error);
+ }
+
+ // Make sure the channel named actually exists.
+ if (!HasChannel(elem->Attr(buzz::QN_NAME)))
+ return BadParse("channel named does not exist", error);
+
+ // Parse the address.
+ if (!ParseAddress(elem, QN_ADDRESS, QN_PORT, addr, error))
+ return false;
+
+ return true;
+}
+
+TransportChannelImpl* RawTransport::CreateTransportChannel(
+ const std::string& name, const std::string& content_type) {
+ return new RawTransportChannel(name, content_type, this,
+ worker_thread(),
+ port_allocator());
+}
+
+void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) {
+ delete channel;
+}
+
+} // namespace cricket
+#endif // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransport.h b/talk/p2p/base/rawtransport.h
new file mode 100644
index 0000000..6734130
--- /dev/null
+++ b/talk/p2p/base/rawtransport.h
@@ -0,0 +1,79 @@
+/*
+ * 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_P2P_BASE_RAWTRANSPORT_H_
+#define TALK_P2P_BASE_RAWTRANSPORT_H_
+
+#include <string>
+#include "talk/p2p/base/transport.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+namespace cricket {
+
+// Implements a transport that only sends raw packets, no STUN. As a result,
+// it cannot do pings to determine connectivity, so it only uses a single port
+// that it thinks will work.
+class RawTransport: public Transport, public TransportParser {
+ public:
+ RawTransport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ PortAllocator* allocator);
+ virtual ~RawTransport();
+
+ virtual bool ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ Candidates* candidates,
+ ParseError* error);
+ virtual bool WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ XmlElements* candidate_elems,
+ WriteError* error);
+
+ protected:
+ // Creates and destroys raw channels.
+ virtual TransportChannelImpl* CreateTransportChannel(
+ const std::string& name, const std::string &content_type);
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel);
+
+ private:
+ // Parses the given element, which should describe the address to use for a
+ // given channel. This will return false and signal an error if the address
+ // or channel name is bad.
+ bool ParseRawAddress(const buzz::XmlElement* elem,
+ talk_base::SocketAddress* addr,
+ ParseError* error);
+
+ friend class RawTransportChannel; // For ParseAddress.
+
+ DISALLOW_EVIL_CONSTRUCTORS(RawTransport);
+};
+
+} // namespace cricket
+
+#endif // defined(FEATURE_ENABLE_PSTN)
+
+#endif // TALK_P2P_BASE_RAWTRANSPORT_H_
diff --git a/talk/p2p/base/rawtransportchannel.cc b/talk/p2p/base/rawtransportchannel.cc
new file mode 100644
index 0000000..39db13c
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.cc
@@ -0,0 +1,278 @@
+/*
+ * 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 "talk/p2p/base/rawtransportchannel.h"
+
+#include <string>
+#include <vector>
+#include "talk/base/common.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/portallocator.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace {
+
+const uint32 MSG_DESTROY_UNUSED_PORTS = 1;
+
+} // namespace
+
+namespace cricket {
+
+RawTransportChannel::RawTransportChannel(const std::string &name,
+ const std::string &content_type,
+ RawTransport* transport,
+ talk_base::Thread *worker_thread,
+ PortAllocator *allocator)
+ : TransportChannelImpl(name, content_type),
+ raw_transport_(transport),
+ allocator_(allocator),
+ allocator_session_(NULL),
+ stun_port_(NULL),
+ relay_port_(NULL),
+ port_(NULL),
+ use_relay_(false) {
+ if (worker_thread == NULL)
+ worker_thread_ = raw_transport_->worker_thread();
+ else
+ worker_thread_ = worker_thread;
+}
+
+RawTransportChannel::~RawTransportChannel() {
+ delete allocator_session_;
+}
+
+int RawTransportChannel::SendPacket(const char *data, size_t size) {
+ if (port_ == NULL)
+ return -1;
+ if (remote_address_.IsAny())
+ return -1;
+ return port_->SendTo(data, size, remote_address_, true);
+}
+
+int RawTransportChannel::SetOption(talk_base::Socket::Option opt, int value) {
+ // TODO: allow these to be set before we have a port
+ if (port_ == NULL)
+ return -1;
+ return port_->SetOption(opt, value);
+}
+
+int RawTransportChannel::GetError() {
+ return (port_ != NULL) ? port_->GetError() : 0;
+}
+
+void RawTransportChannel::Connect() {
+ // Create an allocator that only returns stun and relay ports.
+ allocator_session_ = allocator_->CreateSession(name(), content_type());
+
+ uint32 flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP;
+
+#if !defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ flags |= PORTALLOCATOR_DISABLE_RELAY;
+#endif
+ allocator_session_->set_flags(flags);
+ allocator_session_->SignalPortReady.connect(
+ this, &RawTransportChannel::OnPortReady);
+ allocator_session_->SignalCandidatesReady.connect(
+ this, &RawTransportChannel::OnCandidatesReady);
+
+ // The initial ports will include stun.
+ allocator_session_->GetInitialPorts();
+}
+
+void RawTransportChannel::Reset() {
+ set_readable(false);
+ set_writable(false);
+
+ delete allocator_session_;
+
+ allocator_session_ = NULL;
+ stun_port_ = NULL;
+ relay_port_ = NULL;
+ port_ = NULL;
+ remote_address_ = talk_base::SocketAddress();
+}
+
+void RawTransportChannel::OnCandidate(const Candidate& candidate) {
+ remote_address_ = candidate.address();
+ ASSERT(!remote_address_.IsAny());
+ set_readable(true);
+
+ // We can write once we have a port and a remote address.
+ if (port_ != NULL)
+ SetWritable();
+}
+
+void RawTransportChannel::OnRemoteAddress(
+ const talk_base::SocketAddress& remote_address) {
+ remote_address_ = remote_address;
+ set_readable(true);
+
+ if (port_ != NULL)
+ SetWritable();
+}
+
+// Note about stun classification
+// Code to classify our NAT type and use the relay port if we are behind an
+// asymmetric NAT is under a FEATURE_ENABLE_STUN_CLASSIFICATION #define.
+// To turn this one we will have to enable a second stun address and make sure
+// that the relay server works for raw UDP.
+//
+// Another option is to classify the NAT type early and not offer the raw
+// transport type at all if we can't support it.
+
+void RawTransportChannel::OnPortReady(
+ PortAllocatorSession* session, Port* port) {
+ ASSERT(session == allocator_session_);
+
+ if (port->type() == STUN_PORT_TYPE) {
+ stun_port_ = static_cast<StunPort*>(port);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ // We need a secondary address to determine the NAT type.
+ stun_port_->PrepareSecondaryAddress();
+#endif
+ } else if (port->type() == RELAY_PORT_TYPE) {
+ relay_port_ = static_cast<RelayPort*>(port);
+ } else {
+ ASSERT(false);
+ }
+}
+
+void RawTransportChannel::OnCandidatesReady(
+ PortAllocatorSession *session, const std::vector<Candidate>& candidates) {
+ ASSERT(session == allocator_session_);
+ ASSERT(candidates.size() >= 1);
+
+ // The most recent candidate is the one we haven't seen yet.
+ Candidate c = candidates[candidates.size() - 1];
+
+ if (c.type() == STUN_PORT_TYPE) {
+ ASSERT(stun_port_ != NULL);
+
+#if defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ // We need to wait until we have two addresses.
+ if (stun_port_->candidates().size() < 2)
+ return;
+
+ // This is the second address. If these addresses are the same, then we
+ // are not behind a symmetric NAT. Hence, a stun port should be sufficient.
+ if (stun_port_->candidates()[0].address() ==
+ stun_port_->candidates()[1].address()) {
+ SetPort(stun_port_);
+ return;
+ }
+
+ // We will need to use relay.
+ use_relay_ = true;
+
+ // If we weren't given a relay port, we'll need to request it.
+ if (relay_port_ == NULL) {
+ allocator_session_->StartGetAllPorts();
+ return;
+ }
+
+ // If we already have a relay address, we're good. Otherwise, we will need
+ // to wait until one arrives.
+ if (relay_port_->candidates().size() > 0)
+ SetPort(relay_port_);
+#else // defined(FEATURE_ENABLE_STUN_CLASSIFICATION)
+ // Always use the stun port. We don't classify right now so just assume it
+ // will work fine.
+ SetPort(stun_port_);
+#endif
+ } else if (c.type() == RELAY_PORT_TYPE) {
+ if (use_relay_)
+ SetPort(relay_port_);
+ } else {
+ ASSERT(false);
+ }
+}
+
+void RawTransportChannel::SetPort(Port* port) {
+ ASSERT(port_ == NULL);
+ port_ = port;
+
+ // We don't need any ports other than the one we picked.
+ allocator_session_->StopGetAllPorts();
+ worker_thread_->Post(
+ this, MSG_DESTROY_UNUSED_PORTS, NULL);
+
+ // Send a message to the other client containing our address.
+
+ ASSERT(port_->candidates().size() >= 1);
+ ASSERT(port_->candidates()[0].protocol() == "udp");
+ SignalCandidateReady(this, port_->candidates()[0]);
+
+ // Read all packets from this port.
+ port_->EnablePortPackets();
+ port_->SignalReadPacket.connect(this, &RawTransportChannel::OnReadPacket);
+
+ // We can write once we have a port and a remote address.
+ if (!remote_address_.IsAny())
+ SetWritable();
+}
+
+void RawTransportChannel::SetWritable() {
+ ASSERT(port_ != NULL);
+ ASSERT(!remote_address_.IsAny());
+
+ set_writable(true);
+
+ SignalRouteChange(this, remote_address_);
+}
+
+void RawTransportChannel::OnReadPacket(
+ Port* port, const char* data, size_t size,
+ const talk_base::SocketAddress& addr) {
+ ASSERT(port_ == port);
+ SignalReadPacket(this, data, size);
+}
+
+void RawTransportChannel::OnMessage(talk_base::Message* msg) {
+ ASSERT(msg->message_id == MSG_DESTROY_UNUSED_PORTS);
+ ASSERT(port_ != NULL);
+ if (port_ != stun_port_) {
+ stun_port_->Destroy();
+ stun_port_ = NULL;
+ }
+ if (port_ != relay_port_ && relay_port_ != NULL) {
+ relay_port_->Destroy();
+ relay_port_ = NULL;
+ }
+}
+
+} // namespace cricket
+#endif // defined(FEATURE_ENABLE_PSTN)
diff --git a/talk/p2p/base/rawtransportchannel.h b/talk/p2p/base/rawtransportchannel.h
new file mode 100644
index 0000000..f99c9c4
--- /dev/null
+++ b/talk/p2p/base/rawtransportchannel.h
@@ -0,0 +1,131 @@
+/*
+ * 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_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
+
+#include <string>
+#include <vector>
+#include "talk/base/messagequeue.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/p2p/base/rawtransport.h"
+#include "talk/p2p/base/candidate.h"
+
+#if defined(FEATURE_ENABLE_PSTN)
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class Port;
+class Connection;
+class StunPort;
+class RelayPort;
+class PortAllocator;
+class PortAllocatorSession;
+
+// Implements a channel that just sends bare packets once we have received the
+// address of the other side. We pick a single address to send them based on
+// a simple investigation of NAT type.
+class RawTransportChannel : public TransportChannelImpl,
+ public talk_base::MessageHandler {
+ public:
+ RawTransportChannel(const std::string &name,
+ const std::string &content_type,
+ RawTransport* transport,
+ talk_base::Thread *worker_thread,
+ PortAllocator *allocator);
+ virtual ~RawTransportChannel();
+
+ // Implementation of normal channel packet sending.
+ virtual int SendPacket(const char *data, size_t len);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ // Returns the raw transport that created this channel.
+ virtual Transport* GetTransport() { return raw_transport_; }
+
+ // Creates an allocator session to start figuring out which type of
+ // port we should send to the other client. This will send
+ // SignalAvailableCandidate once we have decided.
+ virtual void Connect();
+
+ // Resets state back to unconnected.
+ virtual void Reset();
+
+ // We don't actually worry about signaling since we can't send new candidates.
+ virtual void OnSignalingReady() {}
+
+ // Handles a message setting the remote address. We are writable once we
+ // have this since we now know where to send.
+ virtual void OnCandidate(const Candidate& candidate);
+
+ void OnRemoteAddress(const talk_base::SocketAddress& remote_address);
+
+
+ private:
+ RawTransport* raw_transport_;
+ talk_base::Thread *worker_thread_;
+ PortAllocator* allocator_;
+ PortAllocatorSession* allocator_session_;
+ StunPort* stun_port_;
+ RelayPort* relay_port_;
+ Port* port_;
+ bool use_relay_;
+ talk_base::SocketAddress remote_address_;
+
+ // Called when the allocator creates another port.
+ void OnPortReady(PortAllocatorSession* session, Port* port);
+
+ // Called when one of the ports we are using has determined its address.
+ void OnCandidatesReady(PortAllocatorSession *session,
+ const std::vector<Candidate>& candidates);
+
+ // Called once we have chosen the port to use for communication with the
+ // other client. This will send its address and prepare the port for use.
+ void SetPort(Port* port);
+
+ // Called once we have a port and a remote address. This will set mark the
+ // channel as writable and signal the route to the client.
+ void SetWritable();
+
+ // Called when we receive a packet from the other client.
+ void OnReadPacket(Port* port, const char* data, size_t size,
+ const talk_base::SocketAddress& addr);
+
+ // Handles a message to destroy unused ports.
+ virtual void OnMessage(talk_base::Message *msg);
+
+ DISALLOW_EVIL_CONSTRUCTORS(RawTransportChannel);
+};
+
+} // namespace cricket
+
+#endif // defined(FEATURE_ENABLE_PSTN)
+#endif // TALK_P2P_BASE_RAWTRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/relayport.cc b/talk/p2p/base/relayport.cc
new file mode 100644
index 0000000..2d3be61
--- /dev/null
+++ b/talk/p2p/base/relayport.cc
@@ -0,0 +1,793 @@
+/*
+ * 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 "talk/base/asyncpacketsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/relayport.h"
+
+namespace cricket {
+
+static const uint32 kMessageConnectTimeout = 1;
+static const int kKeepAliveDelay = 10 * 60 * 1000;
+static const int kRetryTimeout = 50 * 1000; // ICE says 50 secs
+// How long to wait for a socket to connect to remote host in milliseconds
+// before trying another connection.
+static const int kSoftConnectTimeoutMs = 3 * 1000;
+
+// Handles a connection to one address/port/protocol combination for a
+// particular RelayEntry.
+class RelayConnection : public sigslot::has_slots<> {
+ public:
+ RelayConnection(const ProtocolAddress* protocol_address,
+ talk_base::AsyncPacketSocket* socket,
+ talk_base::Thread* thread);
+ ~RelayConnection();
+ talk_base::AsyncPacketSocket* socket() const { return socket_; }
+
+ const ProtocolAddress* protocol_address() {
+ return protocol_address_;
+ }
+
+ talk_base::SocketAddress GetAddress() const {
+ return protocol_address_->address;
+ }
+
+ ProtocolType GetProtocol() const {
+ return protocol_address_->proto;
+ }
+
+ int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+ // Validates a response to a STUN allocate request.
+ bool CheckResponse(StunMessage* msg);
+
+ // Sends data to the relay server.
+ int Send(const void* pv, size_t cb);
+
+ // Sends a STUN allocate request message to the relay server.
+ void SendAllocateRequest(RelayEntry* entry, int delay);
+
+ // Return the latest error generated by the socket.
+ int GetError() { return socket_->GetError(); }
+
+ // Called on behalf of a StunRequest to write data to the socket. This is
+ // already STUN intended for the server, so no wrapping is necessary.
+ void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ private:
+ talk_base::AsyncPacketSocket* socket_;
+ const ProtocolAddress* protocol_address_;
+ StunRequestManager *request_manager_;
+};
+
+// Manages a number of connections to the relayserver, one for each
+// available protocol. We aim to use each connection for only a
+// specific destination address so that we can avoid wrapping every
+// packet in a STUN send / data indication.
+class RelayEntry : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ RelayEntry(RelayPort* port, const talk_base::SocketAddress& ext_addr);
+ ~RelayEntry();
+
+ RelayPort* port() { return port_; }
+
+ const talk_base::SocketAddress& address() const { return ext_addr_; }
+ void set_address(const talk_base::SocketAddress& addr) { ext_addr_ = addr; }
+
+ bool connected() const { return connected_; }
+ bool locked() const { return locked_; }
+
+ // Returns the last error on the socket of this entry.
+ int GetError();
+
+ // Returns the most preferred connection of the given
+ // ones. Connections are rated based on protocol in the order of:
+ // UDP, TCP and SSLTCP, where UDP is the most preferred protocol
+ static RelayConnection* GetBestConnection(RelayConnection* conn1,
+ RelayConnection* conn2);
+
+ // Sends the STUN requests to the server to initiate this connection.
+ void Connect();
+
+ // Called when this entry becomes connected. The address given is the one
+ // exposed to the outside world on the relay server.
+ void OnConnect(const talk_base::SocketAddress& mapped_addr,
+ RelayConnection* socket);
+
+ // Sends a packet to the given destination address using the socket of this
+ // entry. This will wrap the packet in STUN if necessary.
+ int SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr);
+
+ // Schedules a keep-alive allocate request.
+ void ScheduleKeepAlive();
+
+ void SetServerIndex(size_t sindex) { server_index_ = sindex; }
+
+ // Sets this option on the socket of each connection.
+ int SetSocketOption(talk_base::Socket::Option opt, int value);
+
+ size_t ServerIndex() const { return server_index_; }
+
+ // Try a different server address
+ void HandleConnectFailure(talk_base::AsyncPacketSocket* socket);
+
+ // Implementation of the MessageHandler Interface.
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ private:
+ RelayPort* port_;
+ talk_base::SocketAddress ext_addr_;
+ size_t server_index_;
+ bool connected_;
+ bool locked_;
+ RelayConnection* current_connection_;
+
+ // Called when a TCP connection is established or fails
+ void OnSocketConnect(talk_base::AsyncPacketSocket* socket);
+ void OnSocketClose(talk_base::AsyncPacketSocket* socket, int error);
+
+ // Called when a packet is received on this socket.
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ // Sends the given data on the socket to the server with no wrapping. This
+ // returns the number of bytes written or -1 if an error occurred.
+ int SendPacket(const void* data, size_t size);
+};
+
+// Handles an allocate request for a particular RelayEntry.
+class AllocateRequest : public StunRequest {
+ public:
+ AllocateRequest(RelayEntry* entry, RelayConnection* connection);
+ virtual ~AllocateRequest() {}
+
+ virtual void Prepare(StunMessage* request);
+
+ virtual int GetNextDelay();
+
+ virtual void OnResponse(StunMessage* response);
+ virtual void OnErrorResponse(StunMessage* response);
+ virtual void OnTimeout();
+
+ private:
+ RelayEntry* entry_;
+ RelayConnection* connection_;
+ uint32 start_time_;
+};
+
+const std::string RELAY_PORT_TYPE("relay");
+
+RelayPort::RelayPort(
+ talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip, int min_port, int max_port,
+ const std::string& username, const std::string& password,
+ const std::string& magic_cookie)
+ : Port(thread, RELAY_PORT_TYPE, factory, network, ip, min_port, max_port),
+ ready_(false),
+ magic_cookie_(magic_cookie),
+ error_(0) {
+ entries_.push_back(
+ new RelayEntry(this, talk_base::SocketAddress()));
+
+ set_username_fragment(username);
+ set_password(password);
+ if (magic_cookie_.size() == 0)
+ magic_cookie_.append(STUN_MAGIC_COOKIE_VALUE, 4);
+}
+
+RelayPort::~RelayPort() {
+ for (size_t i = 0; i < entries_.size(); ++i)
+ delete entries_[i];
+ thread_->Clear(this);
+}
+
+void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
+ // Since HTTP proxies usually only allow 443,
+ // let's up the priority on PROTO_SSLTCP
+ if (addr.proto == PROTO_SSLTCP &&
+ (proxy().type == talk_base::PROXY_HTTPS ||
+ proxy().type == talk_base::PROXY_UNKNOWN)) {
+ server_addr_.push_front(addr);
+ } else {
+ server_addr_.push_back(addr);
+ }
+}
+
+void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
+ std::string proto_name = ProtoToString(addr.proto);
+ for (std::vector<Candidate>::const_iterator it = candidates().begin();
+ it != candidates().end(); ++it) {
+ if ((it->address() == addr.address) && (it->protocol() == proto_name)) {
+ LOG(INFO) << "Redundant relay address: " << proto_name
+ << " @ " << addr.address.ToString();
+ return;
+ }
+ }
+ AddAddress(addr.address, proto_name, false);
+}
+
+void RelayPort::SetReady() {
+ if (!ready_) {
+ ready_ = true;
+ SignalAddressReady(this);
+ }
+}
+
+const ProtocolAddress * RelayPort::ServerAddress(size_t index) const {
+ if (index < server_addr_.size())
+ return &server_addr_[index];
+ return NULL;
+}
+
+bool RelayPort::HasMagicCookie(const char* data, size_t size) {
+ if (size < 24 + magic_cookie_.size()) {
+ return false;
+ } else {
+ return 0 == std::memcmp(data + 24,
+ magic_cookie_.c_str(),
+ magic_cookie_.size());
+ }
+}
+
+void RelayPort::PrepareAddress() {
+ // We initiate a connect on the first entry. If this completes, it will fill
+ // in the server address as the address of this port.
+ ASSERT(entries_.size() == 1);
+ entries_[0]->Connect();
+ ready_ = false;
+}
+
+Connection* RelayPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // We only create conns to non-udp sockets if they are incoming on this port
+ if ((address.protocol() != "udp") && (origin != ORIGIN_THIS_PORT)) {
+ return 0;
+ }
+
+ // We don't support loopback on relays
+ if (address.type() == type()) {
+ return 0;
+ }
+
+ size_t index = 0;
+ for (size_t i = 0; i < candidates().size(); ++i) {
+ const Candidate& local = candidates()[i];
+ if (local.protocol() == address.protocol()) {
+ index = i;
+ break;
+ }
+ }
+
+ Connection * conn = new ProxyConnection(this, index, address);
+ AddConnection(conn);
+ return conn;
+}
+
+int RelayPort::SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload) {
+ // Try to find an entry for this specific address. Note that the first entry
+ // created was not given an address initially, so it can be set to the first
+ // address that comes along.
+ RelayEntry* entry = 0;
+
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ if (entries_[i]->address().IsAny() && payload) {
+ entry = entries_[i];
+ entry->set_address(addr);
+ break;
+ } else if (entries_[i]->address() == addr) {
+ entry = entries_[i];
+ break;
+ }
+ }
+
+ // If we did not find one, then we make a new one. This will not be useable
+ // until it becomes connected, however.
+ if (!entry && payload) {
+ entry = new RelayEntry(this, addr);
+ if (!entries_.empty()) {
+ entry->SetServerIndex(entries_[0]->ServerIndex());
+ }
+ entry->Connect();
+ entries_.push_back(entry);
+ }
+
+ // If the entry is connected, then we can send on it (though wrapping may
+ // still be necessary). Otherwise, we can't yet use this connection, so we
+ // default to the first one.
+ if (!entry || !entry->connected()) {
+ ASSERT(!entries_.empty());
+ entry = entries_[0];
+ if (!entry->connected()) {
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ }
+
+ // Send the actual contents to the server using the usual mechanism.
+ int sent = entry->SendTo(data, size, addr);
+ if (sent <= 0) {
+ ASSERT(sent < 0);
+ error_ = entry->GetError();
+ return SOCKET_ERROR;
+ }
+ // The caller of the function is expecting the number of user data bytes,
+ // rather than the size of the packet.
+ return size;
+}
+
+int RelayPort::SetOption(talk_base::Socket::Option opt, int value) {
+ int result = 0;
+ for (size_t i = 0; i < entries_.size(); ++i) {
+ if (entries_[i]->SetSocketOption(opt, value) < 0) {
+ result = -1;
+ error_ = entries_[i]->GetError();
+ }
+ }
+ options_.push_back(OptionValue(opt, value));
+ return result;
+}
+
+int RelayPort::GetError() {
+ return error_;
+}
+
+void RelayPort::OnReadPacket(
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr);
+ }
+}
+
+RelayConnection::RelayConnection(const ProtocolAddress* protocol_address,
+ talk_base::AsyncPacketSocket* socket,
+ talk_base::Thread* thread)
+ : socket_(socket),
+ protocol_address_(protocol_address) {
+ request_manager_ = new StunRequestManager(thread);
+ request_manager_->SignalSendPacket.connect(this,
+ &RelayConnection::OnSendPacket);
+}
+
+RelayConnection::~RelayConnection() {
+ delete request_manager_;
+ delete socket_;
+}
+
+int RelayConnection::SetSocketOption(talk_base::Socket::Option opt,
+ int value) {
+ if (socket_) {
+ return socket_->SetOption(opt, value);
+ }
+ return 0;
+}
+
+bool RelayConnection::CheckResponse(StunMessage* msg) {
+ return request_manager_->CheckResponse(msg);
+}
+
+void RelayConnection::OnSendPacket(const void* data, size_t size,
+ StunRequest* req) {
+ int sent = socket_->SendTo(data, size, GetAddress());
+ if (sent <= 0) {
+ LOG(LS_VERBOSE) << "OnSendPacket: failed sending to " << GetAddress() <<
+ std::strerror(socket_->GetError());
+ ASSERT(sent < 0);
+ }
+}
+
+int RelayConnection::Send(const void* pv, size_t cb) {
+ return socket_->SendTo(pv, cb, GetAddress());
+}
+
+void RelayConnection::SendAllocateRequest(RelayEntry* entry, int delay) {
+ request_manager_->SendDelayed(new AllocateRequest(entry, this), delay);
+}
+
+RelayEntry::RelayEntry(RelayPort* port,
+ const talk_base::SocketAddress& ext_addr)
+ : port_(port), ext_addr_(ext_addr),
+ server_index_(0), connected_(false), locked_(false),
+ current_connection_(NULL) {
+}
+
+RelayEntry::~RelayEntry() {
+ // Remove all RelayConnections and dispose sockets.
+ delete current_connection_;
+ current_connection_ = NULL;
+}
+
+void RelayEntry::Connect() {
+ // If we're already connected, return.
+ if (connected_)
+ return;
+
+ // If we've exhausted all options, bail out.
+ const ProtocolAddress* ra = port()->ServerAddress(server_index_);
+ if (!ra) {
+ LOG(LS_WARNING) << "No more relay addresses left to try";
+ return;
+ }
+
+ // Remove any previous connection.
+ if (current_connection_) {
+ port()->thread()->Dispose(current_connection_);
+ current_connection_ = NULL;
+ }
+
+ // Try to set up our new socket.
+ LOG(LS_INFO) << "Connecting to relay via " << ProtoToString(ra->proto) <<
+ " @ " << ra->address.ToString();
+
+ talk_base::AsyncPacketSocket* socket = NULL;
+
+ if (ra->proto == PROTO_UDP) {
+ // UDP sockets are simple.
+ socket = port_->socket_factory()->CreateUdpSocket(
+ talk_base::SocketAddress(port_->ip_, 0),
+ port_->min_port_, port_->max_port_);
+ } else if (ra->proto == PROTO_TCP || ra->proto == PROTO_SSLTCP) {
+ socket = port_->socket_factory()->CreateClientTcpSocket(
+ talk_base::SocketAddress(port_->ip_, 0), ra->address,
+ port_->proxy(), port_->user_agent(), ra->proto == PROTO_SSLTCP);
+ } else {
+ LOG(LS_WARNING) << "Unknown protocol (" << ra->proto << ")";
+ }
+
+ if (!socket) {
+ LOG(LS_WARNING) << "Socket creation failed";
+ }
+
+ // If we failed to get a socket, move on to the next protocol.
+ if (!socket) {
+ port()->thread()->Post(this, kMessageConnectTimeout);
+ return;
+ }
+
+ // Otherwise, create the new connection and configure any socket options.
+ socket->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket);
+ current_connection_ = new RelayConnection(ra, socket, port()->thread());
+ for (size_t i = 0; i < port_->options().size(); ++i) {
+ current_connection_->SetSocketOption(port_->options()[i].first,
+ port_->options()[i].second);
+ }
+
+ // If we're trying UDP, start binding requests.
+ // If we're trying TCP, wait for connection with a fixed timeout.
+ if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) {
+ socket->SignalClose.connect(this, &RelayEntry::OnSocketClose);
+ socket->SignalConnect.connect(this, &RelayEntry::OnSocketConnect);
+ port()->thread()->PostDelayed(kSoftConnectTimeoutMs, this,
+ kMessageConnectTimeout);
+ } else {
+ current_connection_->SendAllocateRequest(this, 0);
+ }
+}
+
+int RelayEntry::GetError() {
+ if (current_connection_ != NULL) {
+ return current_connection_->GetError();
+ }
+ return 0;
+}
+
+RelayConnection* RelayEntry::GetBestConnection(RelayConnection* conn1,
+ RelayConnection* conn2) {
+ return conn1->GetProtocol() <= conn2->GetProtocol() ? conn1 : conn2;
+}
+
+void RelayEntry::OnConnect(const talk_base::SocketAddress& mapped_addr,
+ RelayConnection* connection) {
+ // We are connected, notify our parent.
+ ProtocolType proto = PROTO_UDP;
+ LOG(INFO) << "Relay allocate succeeded: " << ProtoToString(proto)
+ << " @ " << mapped_addr.ToString();
+ connected_ = true;
+
+ port_->AddExternalAddress(ProtocolAddress(mapped_addr, proto));
+ port_->SetReady();
+}
+
+int RelayEntry::SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr) {
+ // If this connection is locked to the address given, then we can send the
+ // packet with no wrapper.
+ if (locked_ && (ext_addr_ == addr))
+ return SendPacket(data, size);
+
+ // Otherwise, we must wrap the given data in a STUN SEND request so that we
+ // can communicate the destination address to the server.
+ //
+ // Note that we do not use a StunRequest here. This is because there is
+ // likely no reason to resend this packet. If it is late, we just drop it.
+ // The next send to this address will try again.
+
+ StunMessage request;
+ request.SetType(STUN_SEND_REQUEST);
+ request.SetTransactionID(talk_base::CreateRandomString(16));
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(port_->magic_cookie().c_str(),
+ port_->magic_cookie().size());
+ request.AddAttribute(magic_cookie_attr);
+
+ StunByteStringAttribute* username_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username_attr->CopyBytes(port_->username_fragment().c_str(),
+ port_->username_fragment().size());
+ request.AddAttribute(username_attr);
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ addr_attr->SetFamily(1);
+ addr_attr->SetIP(addr.ip());
+ addr_attr->SetPort(addr.port());
+ request.AddAttribute(addr_attr);
+
+ // Attempt to lock
+ if (ext_addr_ == addr) {
+ StunUInt32Attribute* options_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_OPTIONS);
+ options_attr->SetValue(0x1);
+ request.AddAttribute(options_attr);
+ }
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ data_attr->CopyBytes(data, size);
+ request.AddAttribute(data_attr);
+
+ // TODO: compute the HMAC.
+
+ talk_base::ByteBuffer buf;
+ request.Write(&buf);
+
+ return SendPacket(buf.Data(), buf.Length());
+}
+
+void RelayEntry::ScheduleKeepAlive() {
+ if (current_connection_) {
+ current_connection_->SendAllocateRequest(this, kKeepAliveDelay);
+ }
+}
+
+int RelayEntry::SetSocketOption(talk_base::Socket::Option opt, int value) {
+ // Set the option on all available sockets.
+ int socket_error = 0;
+ if (current_connection_) {
+ socket_error = current_connection_->SetSocketOption(opt, value);
+ }
+ return socket_error;
+}
+
+void RelayEntry::HandleConnectFailure(
+ talk_base::AsyncPacketSocket* socket) {
+ // Make sure it's the current connection that has failed, it might
+ // be an old socked that has not yet been disposed.
+ if (!socket || socket == current_connection_->socket()) {
+ if (current_connection_)
+ port()->SignalConnectFailure(current_connection_->protocol_address());
+
+ // Try to connect to the next server address.
+ server_index_ += 1;
+ Connect();
+ }
+}
+
+void RelayEntry::OnMessage(talk_base::Message *pmsg) {
+ ASSERT(pmsg->message_id == kMessageConnectTimeout);
+ if (current_connection_) {
+ const ProtocolAddress* ra = current_connection_->protocol_address();
+ LOG(LS_WARNING) << "Relay " << ra->proto << " connection to " <<
+ ra->address << " timed out";
+
+ // Currently we connect to each server address in sequence. If we
+ // have more addresses to try, treat this is an error and move on to
+ // the next address, otherwise give this connection more time and
+ // await the real timeout.
+ //
+ // TODO: Connect to servers in parallel to speed up connect time
+ // and to avoid giving up too early.
+ port_->SignalSoftTimeout(ra);
+ HandleConnectFailure(current_connection_->socket());
+ } else {
+ HandleConnectFailure(NULL);
+ }
+}
+
+void RelayEntry::OnSocketConnect(talk_base::AsyncPacketSocket* socket) {
+ LOG(INFO) << "relay tcp connected to " <<
+ socket->GetRemoteAddress().ToString();
+ if (current_connection_ != NULL) {
+ current_connection_->SendAllocateRequest(this, 0);
+ }
+}
+
+void RelayEntry::OnSocketClose(talk_base::AsyncPacketSocket* socket,
+ int error) {
+ PLOG(LERROR, error) << "Relay connection failed: socket closed";
+ HandleConnectFailure(socket);
+}
+
+void RelayEntry::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ // ASSERT(remote_addr == port_->server_addr());
+ // TODO: are we worried about this?
+
+ if (current_connection_ == NULL || socket != current_connection_->socket()) {
+ // This packet comes from an unknown address.
+ LOG(WARNING) << "Dropping packet: unknown address";
+ return;
+ }
+
+ // If the magic cookie is not present, then this is an unwrapped packet sent
+ // by the server, The actual remote address is the one we recorded.
+ if (!port_->HasMagicCookie(data, size)) {
+ if (locked_) {
+ port_->OnReadPacket(data, size, ext_addr_);
+ } else {
+ LOG(WARNING) << "Dropping packet: entry not locked";
+ }
+ return;
+ }
+
+ talk_base::ByteBuffer buf(data, size);
+ StunMessage msg;
+ if (!msg.Read(&buf)) {
+ LOG(INFO) << "Incoming packet was not STUN";
+ return;
+ }
+
+ // The incoming packet should be a STUN ALLOCATE response, SEND response, or
+ // DATA indication.
+ if (current_connection_->CheckResponse(&msg)) {
+ return;
+ } else if (msg.type() == STUN_SEND_RESPONSE) {
+ if (const StunUInt32Attribute* options_attr =
+ msg.GetUInt32(STUN_ATTR_OPTIONS)) {
+ if (options_attr->value() & 0x1) {
+ locked_ = true;
+ }
+ }
+ return;
+ } else if (msg.type() != STUN_DATA_INDICATION) {
+ LOG(INFO) << "Received BAD stun type from server: " << msg.type();
+ return;
+ }
+
+ // This must be a data indication.
+
+ const StunAddressAttribute* addr_attr =
+ msg.GetAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ if (!addr_attr) {
+ LOG(INFO) << "Data indication has no source address";
+ return;
+ } else if (addr_attr->family() != 1) {
+ LOG(INFO) << "Source address has bad family";
+ return;
+ }
+
+ talk_base::SocketAddress remote_addr2(addr_attr->ip(), addr_attr->port());
+
+ const StunByteStringAttribute* data_attr = msg.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ LOG(INFO) << "Data indication has no data";
+ return;
+ }
+
+ // Process the actual data and remote address in the normal manner.
+ port_->OnReadPacket(data_attr->bytes(), data_attr->length(), remote_addr2);
+}
+
+int RelayEntry::SendPacket(const void* data, size_t size) {
+ int sent = 0;
+ if (current_connection_) {
+ // We are connected, no need to send packets anywere else than to
+ // the current connection.
+ sent = current_connection_->Send(data, size);
+ }
+ return sent;
+}
+
+AllocateRequest::AllocateRequest(RelayEntry* entry,
+ RelayConnection* connection) :
+ entry_(entry), connection_(connection) {
+ start_time_ = talk_base::Time();
+}
+
+void AllocateRequest::Prepare(StunMessage* request) {
+ request->SetType(STUN_ALLOCATE_REQUEST);
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(
+ entry_->port()->magic_cookie().c_str(),
+ entry_->port()->magic_cookie().size());
+ request->AddAttribute(magic_cookie_attr);
+
+ StunByteStringAttribute* username_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_USERNAME);
+ username_attr->CopyBytes(
+ entry_->port()->username_fragment().c_str(),
+ entry_->port()->username_fragment().size());
+ request->AddAttribute(username_attr);
+}
+
+int AllocateRequest::GetNextDelay() {
+ int delay = 100 * talk_base::_max(1 << count_, 2);
+ count_ += 1;
+ if (count_ == 5)
+ timeout_ = true;
+ return delay;
+}
+
+void AllocateRequest::OnResponse(StunMessage* response) {
+ const StunAddressAttribute* addr_attr =
+ response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ if (!addr_attr) {
+ LOG(INFO) << "Allocate response missing mapped address.";
+ } else if (addr_attr->family() != 1) {
+ LOG(INFO) << "Mapped address has bad family";
+ } else {
+ talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+ entry_->OnConnect(addr, connection_);
+ }
+
+ // We will do a keep-alive regardless of whether this request suceeds.
+ // This should have almost no impact on network usage.
+ entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ if (!attr) {
+ LOG(INFO) << "Bad allocate response error code";
+ } else {
+ LOG(INFO) << "Allocate error response:"
+ << " code=" << static_cast<int>(attr->error_code())
+ << " reason='" << attr->reason() << "'";
+ }
+
+ if (talk_base::TimeSince(start_time_) <= kRetryTimeout)
+ entry_->ScheduleKeepAlive();
+}
+
+void AllocateRequest::OnTimeout() {
+ LOG(INFO) << "Allocate request timed out";
+ entry_->HandleConnectFailure(connection_->socket());
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/relayport.h b/talk/p2p/base/relayport.h
new file mode 100644
index 0000000..025668a
--- /dev/null
+++ b/talk/p2p/base/relayport.h
@@ -0,0 +1,115 @@
+/*
+ * 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_P2P_BASE_RELAYPORT_H_
+#define TALK_P2P_BASE_RELAYPORT_H_
+
+#include <deque>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace cricket {
+
+extern const std::string RELAY_PORT_TYPE;
+class RelayEntry;
+class RelayConnection;
+
+// Communicates using an allocated port on the relay server. For each
+// remote candidate that we try to send data to a RelayEntry instance
+// is created. The RelayEntry will try to reach the remote destination
+// by connecting to all available server addresses in a pre defined
+// order with a small delay in between. When a connection is
+// successful all other connection attemts are aborted.
+class RelayPort : public Port {
+ public:
+ typedef std::pair<talk_base::Socket::Option, int> OptionValue;
+
+ // RelayPort doesn't yet do anything fancy in the ctor.
+ static RelayPort* Create(
+ talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip, int min_port, int max_port,
+ const std::string& username, const std::string& password,
+ const std::string& magic_cookie) {
+ return new RelayPort(thread, factory, network, ip, min_port, max_port,
+ username, password, magic_cookie);
+ }
+ virtual ~RelayPort();
+
+ void AddServerAddress(const ProtocolAddress& addr);
+ void AddExternalAddress(const ProtocolAddress& addr);
+
+ const std::vector<OptionValue>& options() const { return options_; }
+ const std::string& magic_cookie() const { return magic_cookie_; }
+ bool HasMagicCookie(const char* data, size_t size);
+
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ const ProtocolAddress * ServerAddress(size_t index) const;
+ bool IsReady() { return ready_; }
+
+ // Used for testing.
+ sigslot::signal1<const ProtocolAddress*> SignalConnectFailure;
+ sigslot::signal1<const ProtocolAddress*> SignalSoftTimeout;
+
+ protected:
+ RelayPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network*, uint32 ip, int min_port, int max_port,
+ const std::string& username, const std::string& password,
+ const std::string& magic_cookie);
+ bool Init();
+
+ void SetReady();
+
+ virtual int SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload);
+
+ // Dispatches the given packet to the port or connection as appropriate.
+ void OnReadPacket(const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ private:
+ friend class RelayEntry;
+
+ std::deque<ProtocolAddress> server_addr_;
+ bool ready_;
+ std::vector<RelayEntry*> entries_;
+ std::vector<OptionValue> options_;
+ std::string magic_cookie_;
+ int error_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_RELAYPORT_H_
diff --git a/talk/p2p/base/relayserver.cc b/talk/p2p/base/relayserver.cc
new file mode 100644
index 0000000..111b340
--- /dev/null
+++ b/talk/p2p/base/relayserver.cc
@@ -0,0 +1,769 @@
+/*
+ * 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 "talk/p2p/base/relayserver.h"
+
+#ifdef POSIX
+#include <errno.h>
+#endif // POSIX
+
+#include <algorithm>
+
+#include "talk/base/asynctcpsocket.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/socketadapters.h"
+
+namespace cricket {
+
+// By default, we require a ping every 90 seconds.
+const int MAX_LIFETIME = 15 * 60 * 1000;
+
+// The number of bytes in each of the usernames we use.
+const uint32 USERNAME_LENGTH = 16;
+
+static const uint32 kMessageAcceptConnection = 1;
+
+// Calls SendTo on the given socket and logs any bad results.
+void Send(talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const talk_base::SocketAddress& addr) {
+ int result = socket->SendTo(bytes, size, addr);
+ if (result < static_cast<int>(size)) {
+ LOG(LS_ERROR) << "SendTo wrote only " << result << " of " << size
+ << " bytes";
+ } else if (result < 0) {
+ LOG_ERR(LS_ERROR) << "SendTo";
+ }
+}
+
+// Sends the given STUN message on the given socket.
+void SendStun(const StunMessage& msg,
+ talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& addr) {
+ talk_base::ByteBuffer buf;
+ msg.Write(&buf);
+ Send(socket, buf.Data(), buf.Length(), addr);
+}
+
+// Constructs a STUN error response and sends it on the given socket.
+void SendStunError(const StunMessage& msg, talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& remote_addr, int error_code,
+ const char* error_desc, const std::string& magic_cookie) {
+ StunMessage err_msg;
+ err_msg.SetType(GetStunErrorResponseType(msg.type()));
+ err_msg.SetTransactionID(msg.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ if (magic_cookie.size() == 0)
+ magic_cookie_attr->CopyBytes(cricket::STUN_MAGIC_COOKIE_VALUE, 4);
+ else
+ magic_cookie_attr->CopyBytes(magic_cookie.c_str(), magic_cookie.size());
+ err_msg.AddAttribute(magic_cookie_attr);
+
+ StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+ err_code->SetErrorClass(error_code / 100);
+ err_code->SetNumber(error_code % 100);
+ err_code->SetReason(error_desc);
+ err_msg.AddAttribute(err_code);
+
+ SendStun(err_msg, socket, remote_addr);
+}
+
+RelayServer::RelayServer(talk_base::Thread* thread)
+ : thread_(thread), log_bindings_(true) {
+}
+
+RelayServer::~RelayServer() {
+ // Deleting the binding will cause it to be removed from the map.
+ while (!bindings_.empty())
+ delete bindings_.begin()->second;
+ for (size_t i = 0; i < internal_sockets_.size(); ++i)
+ delete internal_sockets_[i];
+ for (size_t i = 0; i < external_sockets_.size(); ++i)
+ delete external_sockets_[i];
+ while (!server_sockets_.empty()) {
+ talk_base::AsyncSocket* socket = server_sockets_.begin()->first;
+ server_sockets_.erase(server_sockets_.begin()->first);
+ delete socket;
+ }
+}
+
+void RelayServer::AddInternalSocket(talk_base::AsyncPacketSocket* socket) {
+ ASSERT(internal_sockets_.end() ==
+ std::find(internal_sockets_.begin(), internal_sockets_.end(), socket));
+ internal_sockets_.push_back(socket);
+ socket->SignalReadPacket.connect(this, &RelayServer::OnInternalPacket);
+}
+
+void RelayServer::RemoveInternalSocket(talk_base::AsyncPacketSocket* socket) {
+ SocketList::iterator iter =
+ std::find(internal_sockets_.begin(), internal_sockets_.end(), socket);
+ ASSERT(iter != internal_sockets_.end());
+ internal_sockets_.erase(iter);
+ socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddExternalSocket(talk_base::AsyncPacketSocket* socket) {
+ ASSERT(external_sockets_.end() ==
+ std::find(external_sockets_.begin(), external_sockets_.end(), socket));
+ external_sockets_.push_back(socket);
+ socket->SignalReadPacket.connect(this, &RelayServer::OnExternalPacket);
+}
+
+void RelayServer::RemoveExternalSocket(talk_base::AsyncPacketSocket* socket) {
+ SocketList::iterator iter =
+ std::find(external_sockets_.begin(), external_sockets_.end(), socket);
+ ASSERT(iter != external_sockets_.end());
+ external_sockets_.erase(iter);
+ socket->SignalReadPacket.disconnect(this);
+}
+
+void RelayServer::AddInternalServerSocket(talk_base::AsyncSocket* socket,
+ cricket::ProtocolType proto) {
+ ASSERT(server_sockets_.end() ==
+ server_sockets_.find(socket));
+ server_sockets_[socket] = proto;
+ socket->SignalReadEvent.connect(this, &RelayServer::OnReadEvent);
+}
+
+void RelayServer::RemoveInternalServerSocket(
+ talk_base::AsyncSocket* socket) {
+ ServerSocketMap::iterator iter = server_sockets_.find(socket);
+ ASSERT(iter != server_sockets_.end());
+ server_sockets_.erase(iter);
+ socket->SignalReadEvent.disconnect(this);
+}
+
+int RelayServer::GetConnectionCount() const {
+ return connections_.size();
+}
+
+talk_base::SocketAddressPair RelayServer::GetConnection(int connection) const {
+ int i = 0;
+ for (ConnectionMap::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ if (i == connection) {
+ return it->second->addr_pair();
+ }
+ ++i;
+ }
+ return talk_base::SocketAddressPair();
+}
+
+bool RelayServer::HasConnection(const talk_base::SocketAddress& address) const {
+ for (ConnectionMap::const_iterator it = connections_.begin();
+ it != connections_.end(); ++it) {
+ if (it->second->addr_pair().destination() == address) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void RelayServer::OnReadEvent(talk_base::AsyncSocket* socket) {
+ ServerSocketMap::iterator iter = server_sockets_.find(socket);
+ ASSERT(iter != server_sockets_.end());
+ AcceptConnection(socket);
+}
+
+void RelayServer::OnInternalPacket(
+ talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+
+ // Get the address of the connection we just received on.
+ bool allocated;
+ talk_base::SocketAddressPair ap(
+ remote_addr, socket->GetLocalAddress(&allocated));
+ ASSERT(allocated);
+ ASSERT(!ap.destination().IsAny());
+
+ // If this did not come from an existing connection, it should be a STUN
+ // allocate request.
+ ConnectionMap::iterator piter = connections_.find(ap);
+ if (piter == connections_.end()) {
+ HandleStunAllocate(bytes, size, ap, socket);
+ return;
+ }
+
+ RelayServerConnection* int_conn = piter->second;
+
+ // Handle STUN requests to the server itself.
+ if (int_conn->binding()->HasMagicCookie(bytes, size)) {
+ HandleStun(int_conn, bytes, size);
+ return;
+ }
+
+ // Otherwise, this is a non-wrapped packet that we are to forward. Make sure
+ // that this connection has been locked. (Otherwise, we would not know what
+ // address to forward to.)
+ if (!int_conn->locked()) {
+ LOG(LS_WARNING) << "Dropping packet: connection not locked";
+ return;
+ }
+
+ // Forward this to the destination address into the connection.
+ RelayServerConnection* ext_conn = int_conn->binding()->GetExternalConnection(
+ int_conn->default_destination());
+ if (ext_conn && ext_conn->locked()) {
+ // TODO: Check the HMAC.
+ ext_conn->Send(bytes, size);
+ } else {
+ // This happens very often and is not an error.
+ LOG(LS_INFO) << "Dropping packet: no external connection";
+ }
+}
+
+void RelayServer::OnExternalPacket(
+ talk_base::AsyncPacketSocket* socket, const char* bytes, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+
+ // Get the address of the connection we just received on.
+ bool allocated;
+ talk_base::SocketAddressPair ap(
+ remote_addr, socket->GetLocalAddress(&allocated));
+ ASSERT(allocated);
+ ASSERT(!ap.destination().IsAny());
+
+ // If this connection already exists, then forward the traffic.
+ ConnectionMap::iterator piter = connections_.find(ap);
+ if (piter != connections_.end()) {
+ // TODO: Check the HMAC.
+ RelayServerConnection* ext_conn = piter->second;
+ RelayServerConnection* int_conn =
+ ext_conn->binding()->GetInternalConnection(
+ ext_conn->addr_pair().source());
+ ASSERT(int_conn != NULL);
+ int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+ ext_conn->Lock(); // allow outgoing packets
+ return;
+ }
+
+ // The first packet should always be a STUN / TURN packet. If it isn't, then
+ // we should just ignore this packet.
+ StunMessage msg;
+ talk_base::ByteBuffer buf(bytes, size);
+ if (!msg.Read(&buf)) {
+ LOG(LS_WARNING) << "Dropping packet: first packet not STUN";
+ return;
+ }
+
+ // The initial packet should have a username (which identifies the binding).
+ const StunByteStringAttribute* username_attr =
+ msg.GetByteString(STUN_ATTR_USERNAME);
+ if (!username_attr) {
+ LOG(LS_WARNING) << "Dropping packet: no username";
+ return;
+ }
+
+ uint32 length = talk_base::_min(static_cast<uint32>(username_attr->length()),
+ USERNAME_LENGTH);
+ std::string username(username_attr->bytes(), length);
+ // TODO: Check the HMAC.
+
+ // The binding should already be present.
+ BindingMap::iterator biter = bindings_.find(username);
+ if (biter == bindings_.end()) {
+ LOG(LS_WARNING) << "Dropping packet: no binding with username";
+ return;
+ }
+
+ // Add this authenticted connection to the binding.
+ RelayServerConnection* ext_conn =
+ new RelayServerConnection(biter->second, ap, socket);
+ ext_conn->binding()->AddExternalConnection(ext_conn);
+ AddConnection(ext_conn);
+
+ // We always know where external packets should be forwarded, so we can lock
+ // them from the beginning.
+ ext_conn->Lock();
+
+ // Send this message on the appropriate internal connection.
+ RelayServerConnection* int_conn = ext_conn->binding()->GetInternalConnection(
+ ext_conn->addr_pair().source());
+ ASSERT(int_conn != NULL);
+ int_conn->Send(bytes, size, ext_conn->addr_pair().source());
+}
+
+bool RelayServer::HandleStun(
+ const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr,
+ talk_base::AsyncPacketSocket* socket, std::string* username,
+ StunMessage* msg) {
+
+ // Parse this into a stun message.
+ talk_base::ByteBuffer buf(bytes, size);
+ if (!msg->Read(&buf)) {
+ SendStunError(*msg, socket, remote_addr, 400, "Bad Request", "");
+ return false;
+ }
+
+ // The initial packet should have a username (which identifies the binding).
+ const StunByteStringAttribute* username_attr =
+ msg->GetByteString(STUN_ATTR_USERNAME);
+ if (!username_attr) {
+ SendStunError(*msg, socket, remote_addr, 432, "Missing Username", "");
+ return false;
+ }
+
+ // Record the username if requested.
+ if (username)
+ username->append(username_attr->bytes(), username_attr->length());
+
+ // TODO: Check for unknown attributes (<= 0x7fff)
+
+ return true;
+}
+
+void RelayServer::HandleStunAllocate(
+ const char* bytes, size_t size, const talk_base::SocketAddressPair& ap,
+ talk_base::AsyncPacketSocket* socket) {
+
+ // Make sure this is a valid STUN request.
+ StunMessage request;
+ std::string username;
+ if (!HandleStun(bytes, size, ap.source(), socket, &username, &request))
+ return;
+
+ // Make sure this is a an allocate request.
+ if (request.type() != STUN_ALLOCATE_REQUEST) {
+ SendStunError(request,
+ socket,
+ ap.source(),
+ 600,
+ "Operation Not Supported",
+ "");
+ return;
+ }
+
+ // TODO: Check the HMAC.
+
+ // Find or create the binding for this username.
+
+ RelayServerBinding* binding;
+
+ BindingMap::iterator biter = bindings_.find(username);
+ if (biter != bindings_.end()) {
+ binding = biter->second;
+ } else {
+ // NOTE: In the future, bindings will be created by the bot only. This
+ // else-branch will then disappear.
+
+ // Compute the appropriate lifetime for this binding.
+ uint32 lifetime = MAX_LIFETIME;
+ const StunUInt32Attribute* lifetime_attr =
+ request.GetUInt32(STUN_ATTR_LIFETIME);
+ if (lifetime_attr)
+ lifetime = talk_base::_min(lifetime, lifetime_attr->value() * 1000);
+
+ binding = new RelayServerBinding(this, username, "0", lifetime);
+ binding->SignalTimeout.connect(this, &RelayServer::OnTimeout);
+ bindings_[username] = binding;
+
+ if (log_bindings_) {
+ LOG(LS_INFO) << "Added new binding " << username << ", "
+ << bindings_.size() << " total";
+ }
+ }
+
+ // Add this connection to the binding. It starts out unlocked.
+ RelayServerConnection* int_conn =
+ new RelayServerConnection(binding, ap, socket);
+ binding->AddInternalConnection(int_conn);
+ AddConnection(int_conn);
+
+ // Now that we have a connection, this other method takes over.
+ HandleStunAllocate(int_conn, request);
+}
+
+void RelayServer::HandleStun(
+ RelayServerConnection* int_conn, const char* bytes, size_t size) {
+
+ // Make sure this is a valid STUN request.
+ StunMessage request;
+ std::string username;
+ if (!HandleStun(bytes, size, int_conn->addr_pair().source(),
+ int_conn->socket(), &username, &request))
+ return;
+
+ // Make sure the username is the one were were expecting.
+ if (username != int_conn->binding()->username()) {
+ int_conn->SendStunError(request, 430, "Stale Credentials");
+ return;
+ }
+
+ // TODO: Check the HMAC.
+
+ // Send this request to the appropriate handler.
+ if (request.type() == STUN_SEND_REQUEST)
+ HandleStunSend(int_conn, request);
+ else if (request.type() == STUN_ALLOCATE_REQUEST)
+ HandleStunAllocate(int_conn, request);
+ else
+ int_conn->SendStunError(request, 600, "Operation Not Supported");
+}
+
+void RelayServer::HandleStunAllocate(
+ RelayServerConnection* int_conn, const StunMessage& request) {
+
+ // Create a response message that includes an address with which external
+ // clients can communicate.
+
+ StunMessage response;
+ response.SetType(STUN_ALLOCATE_RESPONSE);
+ response.SetTransactionID(request.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+ int_conn->binding()->magic_cookie().size());
+ response.AddAttribute(magic_cookie_attr);
+
+ size_t index = rand() % external_sockets_.size();
+ bool allocated;
+ talk_base::SocketAddress ext_addr =
+ external_sockets_[index]->GetLocalAddress(&allocated);
+ ASSERT(allocated);
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ addr_attr->SetFamily(1);
+ addr_attr->SetIP(ext_addr.ip());
+ addr_attr->SetPort(ext_addr.port());
+ response.AddAttribute(addr_attr);
+
+ StunUInt32Attribute* res_lifetime_attr =
+ StunAttribute::CreateUInt32(STUN_ATTR_LIFETIME);
+ res_lifetime_attr->SetValue(int_conn->binding()->lifetime() / 1000);
+ response.AddAttribute(res_lifetime_attr);
+
+ // TODO: Support transport-prefs (preallocate RTCP port).
+ // TODO: Support bandwidth restrictions.
+ // TODO: Add message integrity check.
+
+ // Send a response to the caller.
+ int_conn->SendStun(response);
+}
+
+void RelayServer::HandleStunSend(
+ RelayServerConnection* int_conn, const StunMessage& request) {
+
+ const StunAddressAttribute* addr_attr =
+ request.GetAddress(STUN_ATTR_DESTINATION_ADDRESS);
+ if (!addr_attr) {
+ int_conn->SendStunError(request, 400, "Bad Request");
+ return;
+ }
+
+ const StunByteStringAttribute* data_attr =
+ request.GetByteString(STUN_ATTR_DATA);
+ if (!data_attr) {
+ int_conn->SendStunError(request, 400, "Bad Request");
+ return;
+ }
+
+ talk_base::SocketAddress ext_addr(addr_attr->ip(), addr_attr->port());
+ RelayServerConnection* ext_conn =
+ int_conn->binding()->GetExternalConnection(ext_addr);
+ if (!ext_conn) {
+ // Create a new connection to establish the relationship with this binding.
+ ASSERT(external_sockets_.size() == 1);
+ talk_base::AsyncPacketSocket* socket = external_sockets_[0];
+ bool allocated;
+ talk_base::SocketAddressPair ap(
+ ext_addr, socket->GetLocalAddress(&allocated));
+ ASSERT(allocated);
+ ext_conn = new RelayServerConnection(int_conn->binding(), ap, socket);
+ ext_conn->binding()->AddExternalConnection(ext_conn);
+ AddConnection(ext_conn);
+ }
+
+ // If this connection has pinged us, then allow outgoing traffic.
+ if (ext_conn->locked())
+ ext_conn->Send(data_attr->bytes(), data_attr->length());
+
+ const StunUInt32Attribute* options_attr =
+ request.GetUInt32(STUN_ATTR_OPTIONS);
+ if (options_attr && (options_attr->value() & 0x01)) {
+ int_conn->set_default_destination(ext_addr);
+ int_conn->Lock();
+
+ StunMessage response;
+ response.SetType(STUN_SEND_RESPONSE);
+ response.SetTransactionID(request.transaction_id());
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(int_conn->binding()->magic_cookie().c_str(),
+ int_conn->binding()->magic_cookie().size());
+ response.AddAttribute(magic_cookie_attr);
+
+ StunUInt32Attribute* options2_attr =
+ StunAttribute::CreateUInt32(cricket::STUN_ATTR_OPTIONS);
+ options2_attr->SetValue(0x01);
+ response.AddAttribute(options2_attr);
+
+ int_conn->SendStun(response);
+ }
+}
+
+void RelayServer::AddConnection(RelayServerConnection* conn) {
+ ASSERT(connections_.find(conn->addr_pair()) == connections_.end());
+ connections_[conn->addr_pair()] = conn;
+}
+
+void RelayServer::RemoveConnection(RelayServerConnection* conn) {
+ ConnectionMap::iterator iter = connections_.find(conn->addr_pair());
+ ASSERT(iter != connections_.end());
+ connections_.erase(iter);
+}
+
+void RelayServer::RemoveBinding(RelayServerBinding* binding) {
+ BindingMap::iterator iter = bindings_.find(binding->username());
+ ASSERT(iter != bindings_.end());
+ bindings_.erase(iter);
+
+ if (log_bindings_) {
+ LOG(LS_INFO) << "Removed binding " << binding->username() << ", "
+ << bindings_.size() << " remaining";
+ }
+}
+
+void RelayServer::OnMessage(talk_base::Message *pmsg) {
+ ASSERT(pmsg->message_id == kMessageAcceptConnection);
+ talk_base::MessageData* data = pmsg->pdata;
+ talk_base::AsyncSocket* socket =
+ static_cast <talk_base::TypedMessageData<talk_base::AsyncSocket*>*>
+ (data)->data();
+ AcceptConnection(socket);
+ delete data;
+}
+
+void RelayServer::OnTimeout(RelayServerBinding* binding) {
+ // This call will result in all of the necessary clean-up. We can't call
+ // delete here, because you can't delete an object that is signaling you.
+ thread_->Dispose(binding);
+}
+
+void RelayServer::AcceptConnection(talk_base::AsyncSocket* server_socket) {
+ // Check if someone is trying to connect to us.
+ talk_base::SocketAddress accept_addr;
+ talk_base::AsyncSocket* accepted_socket =
+ server_socket->Accept(&accept_addr);
+ if (accepted_socket != NULL) {
+ // We had someone trying to connect, now check which protocol to
+ // use and create a packet socket.
+ ASSERT(server_sockets_[server_socket] == cricket::PROTO_TCP ||
+ server_sockets_[server_socket] == cricket::PROTO_SSLTCP);
+ if (server_sockets_[server_socket] == cricket::PROTO_SSLTCP) {
+ accepted_socket = new talk_base::AsyncSSLServerSocket(accepted_socket);
+ }
+ talk_base::AsyncTCPSocket* tcp_socket =
+ new talk_base::AsyncTCPSocket(accepted_socket, false);
+
+ // Finally add the socket so it can start communicating with the client.
+ AddInternalSocket(tcp_socket);
+ }
+}
+
+RelayServerConnection::RelayServerConnection(
+ RelayServerBinding* binding, const talk_base::SocketAddressPair& addrs,
+ talk_base::AsyncPacketSocket* socket)
+ : binding_(binding), addr_pair_(addrs), socket_(socket), locked_(false) {
+ // The creation of a new connection constitutes a use of the binding.
+ binding_->NoteUsed();
+}
+
+RelayServerConnection::~RelayServerConnection() {
+ // Remove this connection from the server's map (if it exists there).
+ binding_->server()->RemoveConnection(this);
+}
+
+void RelayServerConnection::Send(const char* data, size_t size) {
+ // Note that the binding has been used again.
+ binding_->NoteUsed();
+
+ cricket::Send(socket_, data, size, addr_pair_.source());
+}
+
+void RelayServerConnection::Send(
+ const char* data, size_t size, const talk_base::SocketAddress& from_addr) {
+ // If the from address is known to the client, we don't need to send it.
+ if (locked() && (from_addr == default_dest_)) {
+ Send(data, size);
+ return;
+ }
+
+ // Wrap the given data in a data-indication packet.
+
+ StunMessage msg;
+ msg.SetType(STUN_DATA_INDICATION);
+ msg.SetTransactionID("0000000000000000");
+
+ StunByteStringAttribute* magic_cookie_attr =
+ StunAttribute::CreateByteString(cricket::STUN_ATTR_MAGIC_COOKIE);
+ magic_cookie_attr->CopyBytes(binding_->magic_cookie().c_str(),
+ binding_->magic_cookie().size());
+ msg.AddAttribute(magic_cookie_attr);
+
+ StunAddressAttribute* addr_attr =
+ StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS2);
+ addr_attr->SetFamily(1);
+ addr_attr->SetIP(from_addr.ip());
+ addr_attr->SetPort(from_addr.port());
+ msg.AddAttribute(addr_attr);
+
+ StunByteStringAttribute* data_attr =
+ StunAttribute::CreateByteString(STUN_ATTR_DATA);
+ ASSERT(size <= 65536);
+ data_attr->CopyBytes(data, uint16(size));
+ msg.AddAttribute(data_attr);
+
+ SendStun(msg);
+}
+
+void RelayServerConnection::SendStun(const StunMessage& msg) {
+ // Note that the binding has been used again.
+ binding_->NoteUsed();
+
+ cricket::SendStun(msg, socket_, addr_pair_.source());
+}
+
+void RelayServerConnection::SendStunError(
+ const StunMessage& request, int error_code, const char* error_desc) {
+ // An error does not indicate use. If no legitimate use off the binding
+ // occurs, we want it to be cleaned up even if errors are still occuring.
+
+ cricket::SendStunError(
+ request, socket_, addr_pair_.source(), error_code, error_desc,
+ binding_->magic_cookie());
+}
+
+void RelayServerConnection::Lock() {
+ locked_ = true;
+}
+
+void RelayServerConnection::Unlock() {
+ locked_ = false;
+}
+
+// IDs used for posted messages:
+const uint32 MSG_LIFETIME_TIMER = 1;
+
+RelayServerBinding::RelayServerBinding(
+ RelayServer* server, const std::string& username,
+ const std::string& password, uint32 lifetime)
+ : server_(server), username_(username), password_(password),
+ lifetime_(lifetime) {
+ // For now, every connection uses the standard magic cookie value.
+ magic_cookie_.append(
+ reinterpret_cast<const char*>(STUN_MAGIC_COOKIE_VALUE), 4);
+
+ // Initialize the last-used time to now.
+ NoteUsed();
+
+ // Set the first timeout check.
+ server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+}
+
+RelayServerBinding::~RelayServerBinding() {
+ // Clear the outstanding timeout check.
+ server_->thread()->Clear(this);
+
+ // Clean up all of the connections.
+ for (size_t i = 0; i < internal_connections_.size(); ++i)
+ delete internal_connections_[i];
+ for (size_t i = 0; i < external_connections_.size(); ++i)
+ delete external_connections_[i];
+
+ // Remove this binding from the server's map.
+ server_->RemoveBinding(this);
+}
+
+void RelayServerBinding::AddInternalConnection(RelayServerConnection* conn) {
+ internal_connections_.push_back(conn);
+}
+
+void RelayServerBinding::AddExternalConnection(RelayServerConnection* conn) {
+ external_connections_.push_back(conn);
+}
+
+void RelayServerBinding::NoteUsed() {
+ last_used_ = talk_base::Time();
+}
+
+bool RelayServerBinding::HasMagicCookie(const char* bytes, size_t size) const {
+ if (size < 24 + magic_cookie_.size()) {
+ return false;
+ } else {
+ return 0 == std::memcmp(
+ bytes + 24, magic_cookie_.c_str(), magic_cookie_.size());
+ }
+}
+
+RelayServerConnection* RelayServerBinding::GetInternalConnection(
+ const talk_base::SocketAddress& ext_addr) {
+
+ // Look for an internal connection that is locked to this address.
+ for (size_t i = 0; i < internal_connections_.size(); ++i) {
+ if (internal_connections_[i]->locked() &&
+ (ext_addr == internal_connections_[i]->default_destination()))
+ return internal_connections_[i];
+ }
+
+ // If one was not found, we send to the first connection.
+ ASSERT(internal_connections_.size() > 0);
+ return internal_connections_[0];
+}
+
+RelayServerConnection* RelayServerBinding::GetExternalConnection(
+ const talk_base::SocketAddress& ext_addr) {
+ for (size_t i = 0; i < external_connections_.size(); ++i) {
+ if (ext_addr == external_connections_[i]->addr_pair().source())
+ return external_connections_[i];
+ }
+ return 0;
+}
+
+void RelayServerBinding::OnMessage(talk_base::Message *pmsg) {
+ if (pmsg->message_id == MSG_LIFETIME_TIMER) {
+ ASSERT(!pmsg->pdata);
+
+ // If the lifetime timeout has been exceeded, then send a signal.
+ // Otherwise, just keep waiting.
+ if (talk_base::Time() >= last_used_ + lifetime_) {
+ LOG(LS_INFO) << "Expiring binding " << username_;
+ SignalTimeout(this);
+ } else {
+ server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER);
+ }
+
+ } else {
+ ASSERT(false);
+ }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/relayserver.h b/talk/p2p/base/relayserver.h
new file mode 100644
index 0000000..d34c099
--- /dev/null
+++ b/talk/p2p/base/relayserver.h
@@ -0,0 +1,249 @@
+/*
+ * 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_P2P_BASE_RELAYSERVER_H_
+#define TALK_P2P_BASE_RELAYSERVER_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/socketaddresspair.h"
+#include "talk/base/thread.h"
+#include "talk/base/time.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+class RelayServerBinding;
+class RelayServerConnection;
+
+// Relays traffic between connections to the server that are "bound" together.
+// All connections created with the same username/password are bound together.
+class RelayServer : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ // Creates a server, which will use this thread to post messages to itself.
+ explicit RelayServer(talk_base::Thread* thread);
+ ~RelayServer();
+
+ talk_base::Thread* thread() { return thread_; }
+
+ // Indicates whether we will print updates of the number of bindings.
+ bool log_bindings() const { return log_bindings_; }
+ void set_log_bindings(bool log_bindings) { log_bindings_ = log_bindings; }
+
+ // Updates the set of sockets that the server uses to talk to "internal"
+ // clients. These are clients that do the "port allocations".
+ void AddInternalSocket(talk_base::AsyncPacketSocket* socket);
+ void RemoveInternalSocket(talk_base::AsyncPacketSocket* socket);
+
+ // Updates the set of sockets that the server uses to talk to "external"
+ // clients. These are the clients that do not do allocations. They do not
+ // know that these addresses represent a relay server.
+ void AddExternalSocket(talk_base::AsyncPacketSocket* socket);
+ void RemoveExternalSocket(talk_base::AsyncPacketSocket* socket);
+
+ // Starts listening for connections on this sockets. When someone
+ // tries to connect, the connection will be accepted and a new
+ // internal socket will be added.
+ void AddInternalServerSocket(talk_base::AsyncSocket* socket,
+ cricket::ProtocolType proto);
+
+ // Removes this server socket from the list.
+ void RemoveInternalServerSocket(talk_base::AsyncSocket* socket);
+
+ // Methods for testing and debuging.
+ int GetConnectionCount() const;
+ talk_base::SocketAddressPair GetConnection(int connection) const;
+ bool HasConnection(const talk_base::SocketAddress& address) const;
+
+ private:
+ typedef std::vector<talk_base::AsyncPacketSocket*> SocketList;
+ typedef std::map<talk_base::AsyncSocket*,
+ cricket::ProtocolType> ServerSocketMap;
+ typedef std::map<std::string, RelayServerBinding*> BindingMap;
+ typedef std::map<talk_base::SocketAddressPair,
+ RelayServerConnection*> ConnectionMap;
+
+ talk_base::Thread* thread_;
+ bool log_bindings_;
+ SocketList internal_sockets_;
+ SocketList external_sockets_;
+ ServerSocketMap server_sockets_;
+ BindingMap bindings_;
+ ConnectionMap connections_;
+
+ // Called when a packet is received by the server on one of its sockets.
+ void OnInternalPacket(talk_base::AsyncPacketSocket* socket,
+ const char* bytes, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+ void OnExternalPacket(talk_base::AsyncPacketSocket* socket,
+ const char* bytes, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ void OnReadEvent(talk_base::AsyncSocket* socket);
+
+ // Processes the relevant STUN request types from the client.
+ bool HandleStun(const char* bytes, size_t size,
+ const talk_base::SocketAddress& remote_addr,
+ talk_base::AsyncPacketSocket* socket,
+ std::string* username, StunMessage* msg);
+ void HandleStunAllocate(const char* bytes, size_t size,
+ const talk_base::SocketAddressPair& ap,
+ talk_base::AsyncPacketSocket* socket);
+ void HandleStun(RelayServerConnection* int_conn, const char* bytes,
+ size_t size);
+ void HandleStunAllocate(RelayServerConnection* int_conn,
+ const StunMessage& msg);
+ void HandleStunSend(RelayServerConnection* int_conn, const StunMessage& msg);
+
+ // Adds/Removes the a connection or binding.
+ void AddConnection(RelayServerConnection* conn);
+ void RemoveConnection(RelayServerConnection* conn);
+ void RemoveBinding(RelayServerBinding* binding);
+
+ // Handle messages in our worker thread.
+ void OnMessage(talk_base::Message *pmsg);
+
+ // Called when the timer for checking lifetime times out.
+ void OnTimeout(RelayServerBinding* binding);
+
+ // Accept connections on this server socket.
+ void AcceptConnection(talk_base::AsyncSocket* server_socket);
+
+ friend class RelayServerConnection;
+ friend class RelayServerBinding;
+};
+
+// Maintains information about a connection to the server. Each connection is
+// part of one and only one binding.
+class RelayServerConnection {
+ public:
+ RelayServerConnection(RelayServerBinding* binding,
+ const talk_base::SocketAddressPair& addrs,
+ talk_base::AsyncPacketSocket* socket);
+ ~RelayServerConnection();
+
+ RelayServerBinding* binding() { return binding_; }
+ talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+ // Returns a pair where the source is the remote address and the destination
+ // is the local address.
+ const talk_base::SocketAddressPair& addr_pair() { return addr_pair_; }
+
+ // Sends a packet to the connected client. If an address is provided, then
+ // we make sure the internal client receives it, wrapping if necessary.
+ void Send(const char* data, size_t size);
+ void Send(const char* data, size_t size,
+ const talk_base::SocketAddress& ext_addr);
+
+ // Sends a STUN message to the connected client with no wrapping.
+ void SendStun(const StunMessage& msg);
+ void SendStunError(const StunMessage& request, int code, const char* desc);
+
+ // A locked connection is one for which we know the intended destination of
+ // any raw packet received.
+ bool locked() const { return locked_; }
+ void Lock();
+ void Unlock();
+
+ // Records the address that raw packets should be forwarded to (for internal
+ // packets only; for external, we already know where they go).
+ const talk_base::SocketAddress& default_destination() const {
+ return default_dest_;
+ }
+ void set_default_destination(const talk_base::SocketAddress& addr) {
+ default_dest_ = addr;
+ }
+
+ private:
+ RelayServerBinding* binding_;
+ talk_base::SocketAddressPair addr_pair_;
+ talk_base::AsyncPacketSocket* socket_;
+ bool locked_;
+ talk_base::SocketAddress default_dest_;
+};
+
+// Records a set of internal and external connections that we relay between,
+// or in other words, that are "bound" together.
+class RelayServerBinding : public talk_base::MessageHandler {
+ public:
+ RelayServerBinding(
+ RelayServer* server, const std::string& username,
+ const std::string& password, uint32 lifetime);
+ virtual ~RelayServerBinding();
+
+ RelayServer* server() { return server_; }
+ uint32 lifetime() { return lifetime_; }
+ const std::string& username() { return username_; }
+ const std::string& password() { return password_; }
+ const std::string& magic_cookie() { return magic_cookie_; }
+
+ // Adds/Removes a connection into the binding.
+ void AddInternalConnection(RelayServerConnection* conn);
+ void AddExternalConnection(RelayServerConnection* conn);
+
+ // We keep track of the use of each binding. If we detect that it was not
+ // used for longer than the lifetime, then we send a signal.
+ void NoteUsed();
+ sigslot::signal1<RelayServerBinding*> SignalTimeout;
+
+ // Determines whether the given packet has the magic cookie present (in the
+ // right place).
+ bool HasMagicCookie(const char* bytes, size_t size) const;
+
+ // Determines the connection to use to send packets to or from the given
+ // external address.
+ RelayServerConnection* GetInternalConnection(
+ const talk_base::SocketAddress& ext_addr);
+ RelayServerConnection* GetExternalConnection(
+ const talk_base::SocketAddress& ext_addr);
+
+ // MessageHandler:
+ void OnMessage(talk_base::Message *pmsg);
+
+ private:
+ RelayServer* server_;
+
+ std::string username_;
+ std::string password_;
+ std::string magic_cookie_;
+
+ std::vector<RelayServerConnection*> internal_connections_;
+ std::vector<RelayServerConnection*> external_connections_;
+
+ uint32 lifetime_;
+ uint32 last_used_;
+ // TODO: bandwidth
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_RELAYSERVER_H_
diff --git a/talk/p2p/base/relayserver_main.cc b/talk/p2p/base/relayserver_main.cc
new file mode 100644
index 0000000..2de4247
--- /dev/null
+++ b/talk/p2p/base/relayserver_main.cc
@@ -0,0 +1,80 @@
+/*
+ * 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 <iostream> // NOLINT
+
+#include "talk/base/thread.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/relayserver.h"
+
+int main(int argc, char **argv) {
+ if (argc != 3) {
+ std::cerr << "usage: relayserver internal-address external-address"
+ << std::endl;
+ return 1;
+ }
+
+ talk_base::SocketAddress int_addr;
+ if (!int_addr.FromString(argv[1])) {
+ std::cerr << "Unable to parse IP address: " << argv[1];
+ return 1;
+ }
+
+ talk_base::SocketAddress ext_addr;
+ if (!ext_addr.FromString(argv[2])) {
+ std::cerr << "Unable to parse IP address: " << argv[2];
+ return 1;
+ }
+
+ talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+ talk_base::scoped_ptr<talk_base::AsyncUDPSocket> int_socket(
+ talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), int_addr));
+ if (!int_socket.get()) {
+ std::cerr << "Failed to create a UDP socket bound at"
+ << int_addr.ToString() << std::endl;
+ return 1;
+ }
+
+ talk_base::scoped_ptr<talk_base::AsyncUDPSocket> ext_socket(
+ talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), ext_addr));
+ if (ext_socket.get()) {
+ std::cerr << "Failed to create a UDP socket bound at"
+ << ext_addr.ToString() << std::endl;
+ return 1;
+ }
+
+ cricket::RelayServer server(pthMain);
+ server.AddInternalSocket(int_socket.get());
+ server.AddExternalSocket(ext_socket.get());
+
+ std::cout << "Listening internally at " << int_addr.ToString() << std::endl;
+ std::cout << "Listening externally at " << ext_addr.ToString() << std::endl;
+
+ pthMain->Run();
+ return 0;
+}
diff --git a/talk/p2p/base/session.cc b/talk/p2p/base/session.cc
new file mode 100644
index 0000000..27b39dd
--- /dev/null
+++ b/talk/p2p/base/session.cc
@@ -0,0 +1,1091 @@
+/*
+ * 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 "talk/p2p/base/session.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelproxy.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+
+#include "talk/p2p/base/constants.h"
+
+namespace {
+
+const uint32 MSG_TIMEOUT = 1;
+const uint32 MSG_ERROR = 2;
+const uint32 MSG_STATE = 3;
+
+} // namespace
+
+namespace cricket {
+
+bool BadMessage(const buzz::QName type,
+ const std::string& text,
+ MessageError* err) {
+ err->SetType(type);
+ err->SetText(text);
+ return false;
+}
+
+TransportProxy::~TransportProxy() {
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ iter->second->SignalDestroyed(iter->second);
+ delete iter->second;
+ }
+ delete transport_;
+}
+
+std::string TransportProxy::type() const {
+ return transport_->type();
+}
+
+TransportChannel* TransportProxy::GetChannel(const std::string& name) {
+ return GetProxy(name);
+}
+
+TransportChannel* TransportProxy::CreateChannel(
+ const std::string& name, const std::string& content_type) {
+ ASSERT(GetChannel(name) == NULL);
+ ASSERT(!transport_->HasChannel(name));
+
+ // We always create a proxy in case we need to change out the transport later.
+ TransportChannelProxy* channel =
+ new TransportChannelProxy(name, content_type);
+ channels_[name] = channel;
+
+ if (state_ == STATE_NEGOTIATED) {
+ SetProxyImpl(name, channel);
+ } else if (state_ == STATE_CONNECTING) {
+ GetOrCreateImpl(name, content_type);
+ }
+ return channel;
+}
+
+void TransportProxy::DestroyChannel(const std::string& name) {
+ TransportChannel* channel = GetChannel(name);
+ if (channel) {
+ channels_.erase(name);
+ channel->SignalDestroyed(channel);
+ delete channel;
+ }
+}
+
+void TransportProxy::SpeculativelyConnectChannels() {
+ ASSERT(state_ == STATE_INIT || state_ == STATE_CONNECTING);
+ state_ = STATE_CONNECTING;
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ GetOrCreateImpl(iter->first, iter->second->content_type());
+ }
+ transport_->ConnectChannels();
+}
+
+void TransportProxy::CompleteNegotiation() {
+ if (state_ != STATE_NEGOTIATED) {
+ state_ = STATE_NEGOTIATED;
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end(); ++iter) {
+ SetProxyImpl(iter->first, iter->second);
+ }
+ transport_->ConnectChannels();
+ }
+}
+
+void TransportProxy::AddSentCandidates(const Candidates& candidates) {
+ for (Candidates::const_iterator cand = candidates.begin();
+ cand != candidates.end(); ++cand) {
+ sent_candidates_.push_back(*cand);
+ }
+}
+
+
+TransportChannelProxy* TransportProxy::GetProxy(const std::string& name) {
+ ChannelMap::iterator iter = channels_.find(name);
+ return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+TransportChannelImpl* TransportProxy::GetOrCreateImpl(
+ const std::string& name, const std::string& content_type) {
+ TransportChannelImpl* impl = transport_->GetChannel(name);
+ if (impl == NULL) {
+ impl = transport_->CreateChannel(name, content_type);
+ }
+ return impl;
+}
+
+void TransportProxy::SetProxyImpl(
+ const std::string& name, TransportChannelProxy* proxy) {
+ TransportChannelImpl* impl = GetOrCreateImpl(name, proxy->content_type());
+ ASSERT(impl != NULL);
+ proxy->SetImplementation(impl);
+}
+
+
+
+
+BaseSession::BaseSession(talk_base::Thread *signaling_thread)
+ : state_(STATE_INIT), error_(ERROR_NONE),
+ local_description_(NULL), remote_description_(NULL),
+ signaling_thread_(signaling_thread) {
+}
+
+BaseSession::~BaseSession() {
+ delete remote_description_;
+ delete local_description_;
+}
+
+void BaseSession::SetState(State state) {
+ ASSERT(signaling_thread_->IsCurrent());
+ if (state != state_) {
+ state_ = state;
+ SignalState(this, state_);
+ signaling_thread_->Post(this, MSG_STATE);
+ }
+}
+
+void BaseSession::SetError(Error error) {
+ ASSERT(signaling_thread_->IsCurrent());
+ if (error != error_) {
+ error_ = error;
+ SignalError(this, error);
+ }
+}
+
+void BaseSession::OnMessage(talk_base::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_TIMEOUT:
+ // Session timeout has occured.
+ SetError(ERROR_TIME);
+ break;
+
+ case MSG_ERROR:
+ TerminateWithReason(STR_TERMINATE_ERROR);
+ break;
+
+ case MSG_STATE:
+ switch (state_) {
+ case STATE_SENTACCEPT:
+ case STATE_RECEIVEDACCEPT:
+ SetState(STATE_INPROGRESS);
+ break;
+
+ case STATE_SENTREJECT:
+ case STATE_RECEIVEDREJECT:
+ // Assume clean termination.
+ Terminate();
+ break;
+
+ default:
+ // Explicitly ignoring some states here.
+ break;
+ }
+ break;
+ }
+}
+
+
+Session::Session(SessionManager *session_manager,
+ const std::string& local_name,
+ const std::string& initiator_name,
+ const std::string& sid, const std::string& content_type,
+ SessionClient* client) :
+ BaseSession(session_manager->signaling_thread()) {
+ ASSERT(session_manager->signaling_thread()->IsCurrent());
+ ASSERT(client != NULL);
+ session_manager_ = session_manager;
+ local_name_ = local_name;
+ sid_ = sid;
+ initiator_name_ = initiator_name;
+ content_type_ = content_type;
+ // TODO: Once we support different transport types,
+ // don't hard code this here.
+ transport_type_ = NS_GINGLE_P2P;
+ transport_parser_ = new P2PTransportParser();
+ client_ = client;
+ error_ = ERROR_NONE;
+ state_ = STATE_INIT;
+ initiator_ = false;
+ current_protocol_ = PROTOCOL_HYBRID;
+}
+
+Session::~Session() {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ ASSERT(state_ != STATE_DEINIT);
+ state_ = STATE_DEINIT;
+ SignalState(this, state_);
+
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ delete iter->second;
+ }
+
+ delete transport_parser_;
+}
+
+Transport* Session::GetTransport(const std::string& content_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy == NULL)
+ return NULL;
+ return transproxy->impl();
+}
+
+void Session::set_allow_local_ips(bool allow) {
+ allow_local_ips_ = allow;
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->impl()->set_allow_local_ips(allow);
+ }
+}
+
+bool Session::Initiate(const std::string &to,
+ const SessionDescription* sdesc) {
+ ASSERT(signaling_thread_->IsCurrent());
+ SessionError error;
+
+ // Only from STATE_INIT
+ if (state_ != STATE_INIT)
+ return false;
+
+ // Setup for signaling.
+ remote_name_ = to;
+ initiator_ = true;
+ set_local_description(sdesc);
+ if (!CreateTransportProxies(GetEmptyTransportInfos(sdesc->contents()),
+ &error)) {
+ LOG(LS_ERROR) << "Could not create transports: " << error.text;
+ return false;
+ }
+
+ if (!SendInitiateMessage(sdesc, &error)) {
+ LOG(LS_ERROR) << "Could not send initiate message: " << error.text;
+ return false;
+ }
+
+ SetState(Session::STATE_SENTINITIATE);
+
+ SpeculativelyConnectAllTransportChannels();
+ return true;
+}
+
+bool Session::Accept(const SessionDescription* sdesc) {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ // Only if just received initiate
+ if (state_ != STATE_RECEIVEDINITIATE)
+ return false;
+
+ // Setup for signaling.
+ initiator_ = false;
+ set_local_description(sdesc);
+
+ SessionError error;
+ if (!SendAcceptMessage(sdesc, &error)) {
+ LOG(LS_ERROR) << "Could not send accept message: " << error.text;
+ return false;
+ }
+
+ SetState(Session::STATE_SENTACCEPT);
+ return true;
+}
+
+bool Session::Reject(const std::string& reason) {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ // Reject is sent in response to an initiate or modify, to reject the
+ // request
+ if (state_ != STATE_RECEIVEDINITIATE && state_ != STATE_RECEIVEDMODIFY)
+ return false;
+
+ // Setup for signaling.
+ initiator_ = false;
+
+ SessionError error;
+ if (!SendRejectMessage(reason, &error)) {
+ LOG(LS_ERROR) << "Could not send reject message: " << error.text;
+ return false;
+ }
+
+ SetState(STATE_SENTREJECT);
+ return true;
+}
+
+bool Session::TerminateWithReason(const std::string& reason) {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ // Either side can terminate, at any time.
+ switch (state_) {
+ case STATE_SENTTERMINATE:
+ case STATE_RECEIVEDTERMINATE:
+ return false;
+
+ case STATE_SENTREJECT:
+ case STATE_RECEIVEDREJECT:
+ // We don't need to send terminate if we sent or received a reject...
+ // it's implicit.
+ break;
+
+ default:
+ SessionError error;
+ if (!SendTerminateMessage(reason, &error)) {
+ LOG(LS_ERROR) << "Could not send terminate message: " << error.text;
+ return false;
+ }
+ break;
+ }
+
+ SetState(STATE_SENTTERMINATE);
+ return true;
+}
+
+bool Session::SendInfoMessage(const XmlElements& elems) {
+ ASSERT(signaling_thread_->IsCurrent());
+ SessionError error;
+ if (!SendMessage(ACTION_SESSION_INFO, elems, &error)) {
+ LOG(LS_ERROR) << "Could not send info message " << error.text;
+ return false;
+ }
+ return true;
+}
+
+
+TransportProxy* Session::GetTransportProxy(const Transport* transport) {
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ TransportProxy* transproxy = iter->second;
+ if (transproxy->impl() == transport) {
+ return transproxy;
+ }
+ }
+ return NULL;
+}
+
+TransportProxy* Session::GetTransportProxy(const std::string& content_name) {
+ TransportMap::iterator iter = transports_.find(content_name);
+ return (iter != transports_.end()) ? iter->second : NULL;
+}
+
+TransportProxy* Session::GetFirstTransportProxy() {
+ if (transports_.empty())
+ return NULL;
+ return transports_.begin()->second;
+}
+
+TransportInfos Session::GetEmptyTransportInfos(
+ const ContentInfos& contents) const {
+ TransportInfos tinfos;
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ tinfos.push_back(
+ TransportInfo(content->name, transport_type_, Candidates()));
+ }
+ return tinfos;
+}
+
+
+bool Session::OnRemoteCandidates(
+ const TransportInfos& tinfos, ParseError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ TransportProxy* transproxy = GetTransportProxy(tinfo->content_name);
+ if (transproxy == NULL) {
+ return BadParse("Unknown content name: " + tinfo->content_name, error);
+ }
+
+ // Must complete negotiation before sending remote candidates, or
+ // there won't be any channel impls.
+ transproxy->CompleteNegotiation();
+ for (Candidates::const_iterator cand = tinfo->candidates.begin();
+ cand != tinfo->candidates.end(); ++cand) {
+ if (!transproxy->impl()->VerifyCandidate(*cand, error))
+ return false;
+
+ if (!transproxy->impl()->HasChannel(cand->name())) {
+ buzz::XmlElement* extra_info =
+ new buzz::XmlElement(QN_GINGLE_P2P_UNKNOWN_CHANNEL_NAME);
+ extra_info->AddAttr(buzz::QN_NAME, cand->name());
+ error->extra = extra_info;
+
+ return BadParse("channel named in candidate does not exist: " +
+ cand->name() + " for content: "+ tinfo->content_name,
+ error);
+ }
+ }
+ transproxy->impl()->OnRemoteCandidates(tinfo->candidates);
+ }
+
+ return true;
+}
+
+
+TransportProxy* Session::GetOrCreateTransportProxy(
+ const std::string& content_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy)
+ return transproxy;
+
+ Transport* transport =
+ new P2PTransport(signaling_thread_,
+ session_manager_->worker_thread(),
+ session_manager_->port_allocator());
+ transport->set_allow_local_ips(allow_local_ips_);
+ transport->SignalConnecting.connect(
+ this, &Session::OnTransportConnecting);
+ transport->SignalWritableState.connect(
+ this, &Session::OnTransportWritable);
+ transport->SignalRequestSignaling.connect(
+ this, &Session::OnTransportRequestSignaling);
+ transport->SignalCandidatesReady.connect(
+ this, &Session::OnTransportCandidatesReady);
+ transport->SignalTransportError.connect(
+ this, &Session::OnTransportSendError);
+ transport->SignalChannelGone.connect(
+ this, &Session::OnTransportChannelGone);
+
+ transproxy = new TransportProxy(content_name, transport);
+ transports_[content_name] = transproxy;
+
+ return transproxy;
+}
+
+bool Session::CreateTransportProxies(const TransportInfos& tinfos,
+ SessionError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (tinfo->transport_type != transport_type_) {
+ error->SetText("No supported transport in offer.");
+ return false;
+ }
+
+ GetOrCreateTransportProxy(tinfo->content_name);
+ }
+ return true;
+}
+
+void Session::SpeculativelyConnectAllTransportChannels() {
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->SpeculativelyConnectChannels();
+ }
+}
+
+TransportParserMap Session::GetTransportParsers() {
+ TransportParserMap parsers;
+ parsers[transport_type_] = transport_parser_;
+ return parsers;
+}
+
+ContentParserMap Session::GetContentParsers() {
+ ContentParserMap parsers;
+ parsers[content_type_] = client_;
+ return parsers;
+}
+
+TransportChannel* Session::CreateChannel(const std::string& content_name,
+ const std::string& channel_name) {
+ // We create the proxy "on demand" here because we need to support
+ // creating channels at any time, even before we send or receive
+ // initiate messages, which is before we create the transports.
+ TransportProxy* transproxy = GetOrCreateTransportProxy(content_name);
+ return transproxy->CreateChannel(channel_name, content_type_);
+}
+
+TransportChannel* Session::GetChannel(const std::string& content_name,
+ const std::string& channel_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ if (transproxy == NULL)
+ return NULL;
+ else
+ return transproxy->GetChannel(channel_name);
+}
+
+void Session::DestroyChannel(const std::string& content_name,
+ const std::string& channel_name) {
+ TransportProxy* transproxy = GetTransportProxy(content_name);
+ ASSERT(transproxy != NULL);
+ transproxy->DestroyChannel(channel_name);
+}
+
+void Session::OnSignalingReady() {
+ ASSERT(signaling_thread_->IsCurrent());
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ iter->second->impl()->OnSignalingReady();
+ }
+}
+
+void Session::OnTransportConnecting(Transport* transport) {
+ // This is an indication that we should begin watching the writability
+ // state of the transport.
+ OnTransportWritable(transport);
+}
+
+void Session::OnTransportWritable(Transport* transport) {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ // If the transport is not writable, start a timer to make sure that it
+ // becomes writable within a reasonable amount of time. If it does not, we
+ // terminate since we can't actually send data. If the transport is writable,
+ // cancel the timer. Note that writability transitions may occur repeatedly
+ // during the lifetime of the session.
+ signaling_thread_->Clear(this, MSG_TIMEOUT);
+ if (transport->HasChannels() && !transport->writable()) {
+ signaling_thread_->PostDelayed(
+ session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT);
+ }
+}
+
+void Session::OnTransportRequestSignaling(Transport* transport) {
+ ASSERT(signaling_thread_->IsCurrent());
+ SignalRequestSignaling(this);
+}
+
+void Session::OnTransportCandidatesReady(Transport* transport,
+ const Candidates& candidates) {
+ ASSERT(signaling_thread_->IsCurrent());
+ TransportProxy* transproxy = GetTransportProxy(transport);
+ if (transproxy != NULL) {
+ if (!transproxy->negotiated()) {
+ transproxy->AddSentCandidates(candidates);
+ }
+ SessionError error;
+ if (!SendTransportInfoMessage(
+ TransportInfo(transproxy->content_name(), transproxy->type(),
+ candidates),
+ &error)) {
+ LOG(LS_ERROR) << "Could not send transport info message: "
+ << error.text;
+ return;
+ }
+ }
+}
+
+void Session::OnTransportSendError(Transport* transport,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ ASSERT(signaling_thread_->IsCurrent());
+ SignalErrorMessage(this, stanza, name, type, text, extra_info);
+}
+
+void Session::OnTransportChannelGone(Transport* transport,
+ const std::string& name) {
+ ASSERT(signaling_thread_->IsCurrent());
+ SignalChannelGone(this, name);
+}
+
+void Session::OnIncomingMessage(const SessionMessage& msg) {
+ ASSERT(signaling_thread_->IsCurrent());
+ ASSERT(state_ == STATE_INIT || msg.from == remote_name_);
+
+ if (current_protocol_== PROTOCOL_HYBRID) {
+ if (msg.protocol == PROTOCOL_GINGLE) {
+ current_protocol_ = PROTOCOL_GINGLE;
+ } else {
+ current_protocol_ = PROTOCOL_JINGLE;
+ }
+ }
+
+ bool valid = false;
+ MessageError error;
+ switch (msg.type) {
+ case ACTION_SESSION_INITIATE:
+ valid = OnInitiateMessage(msg, &error);
+ break;
+ case ACTION_SESSION_INFO:
+ valid = OnInfoMessage(msg);
+ break;
+ case ACTION_SESSION_ACCEPT:
+ valid = OnAcceptMessage(msg, &error);
+ break;
+ case ACTION_SESSION_REJECT:
+ valid = OnRejectMessage(msg, &error);
+ break;
+ case ACTION_SESSION_TERMINATE:
+ valid = OnTerminateMessage(msg, &error);
+ break;
+ case ACTION_TRANSPORT_INFO:
+ valid = OnTransportInfoMessage(msg, &error);
+ break;
+ case ACTION_TRANSPORT_ACCEPT:
+ valid = OnTransportAcceptMessage(msg, &error);
+ break;
+ case ACTION_NOTIFY:
+ valid = OnNotifyMessage(msg, &error);
+ break;
+ case ACTION_UPDATE:
+ valid = OnUpdateMessage(msg, &error);
+ break;
+ default:
+ valid = BadMessage(buzz::QN_STANZA_BAD_REQUEST,
+ "unknown session message type",
+ &error);
+ }
+
+ if (valid) {
+ SendAcknowledgementMessage(msg.stanza);
+ } else {
+ SignalErrorMessage(this, msg.stanza, error.type,
+ "modify", error.text, NULL);
+ }
+}
+
+void Session::OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza) {
+ ASSERT(signaling_thread_->IsCurrent());
+
+ SessionMessage msg;
+ ParseError parse_error;
+ if (!ParseSessionMessage(orig_stanza, &msg, &parse_error)) {
+ LOG(LS_ERROR) << "Error parsing failed send: " << parse_error.text
+ << ":" << orig_stanza;
+ return;
+ }
+
+ // If the error is a session redirect, call OnRedirectError, which will
+ // continue the session with a new remote JID.
+ SessionRedirect redirect;
+ if (FindSessionRedirect(error_stanza, &redirect)) {
+ SessionError error;
+ if (!OnRedirectError(redirect, &error)) {
+ // TODO: Should we send a message back? The standard
+ // says nothing about it.
+ LOG(LS_ERROR) << "Failed to redirect: " << error.text;
+ SetError(ERROR_RESPONSE);
+ }
+ return;
+ }
+
+ std::string error_type = "cancel";
+
+ const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR);
+ if (error) {
+ ASSERT(error->HasAttr(buzz::QN_TYPE));
+ error_type = error->Attr(buzz::QN_TYPE);
+
+ LOG(LS_ERROR) << "Session error:\n" << error->Str() << "\n"
+ << "in response to:\n" << orig_stanza->Str();
+ } else {
+ // don't crash if <error> is missing
+ LOG(LS_ERROR) << "Session error without <error/> element, ignoring";
+ return;
+ }
+
+ if (msg.type == ACTION_TRANSPORT_INFO) {
+ // Transport messages frequently generate errors because they are sent right
+ // when we detect a network failure. For that reason, we ignore such
+ // errors, because if we do not establish writability again, we will
+ // terminate anyway. The exceptions are transport-specific error tags,
+ // which we pass on to the respective transport.
+
+ // TODO: This is only used for unknown channel name.
+ // For Jingle, find a stanard-compliant way of doing this. For
+ // Gingle, guess the content name based on the channel name.
+ for (const buzz::XmlElement* elem = error->FirstElement();
+ NULL != elem; elem = elem->NextElement()) {
+ TransportProxy* transproxy = GetFirstTransportProxy();
+ if (transproxy && transproxy->type() == error->Name().Namespace()) {
+ transproxy->impl()->OnTransportError(elem);
+ }
+ }
+ } else if ((error_type != "continue") && (error_type != "wait")) {
+ // We do not set an error if the other side said it is okay to continue
+ // (possibly after waiting). These errors can be ignored.
+ SetError(ERROR_RESPONSE);
+ }
+}
+
+bool Session::OnInitiateMessage(const SessionMessage& msg,
+ MessageError* error) {
+ if (!CheckState(STATE_INIT, error))
+ return false;
+
+ SessionInitiate init;
+ if (!ParseSessionInitiate(msg.protocol, msg.action_elem,
+ GetContentParsers(), GetTransportParsers(),
+ &init, error))
+ return false;
+
+ SessionError session_error;
+ if (!CreateTransportProxies(init.transports, &session_error)) {
+ return BadMessage(buzz::QN_STANZA_NOT_ACCEPTABLE,
+ session_error.text, error);
+ }
+
+ initiator_ = false;
+ remote_name_ = msg.from;
+ set_remote_description(new SessionDescription(init.ClearContents()));
+ SetState(STATE_RECEIVEDINITIATE);
+
+ // Users of Session may listen to state change and call Reject().
+ if (state_ != STATE_SENTREJECT) {
+ if (!OnRemoteCandidates(init.transports, error))
+ return false;
+ }
+ return true;
+}
+
+bool Session::OnAcceptMessage(const SessionMessage& msg, MessageError* error) {
+ if (!CheckState(STATE_SENTINITIATE, error))
+ return false;
+
+ SessionAccept accept;
+ if (!ParseSessionAccept(msg.protocol, msg.action_elem,
+ GetContentParsers(), GetTransportParsers(),
+ &accept, error))
+ return false;
+
+ set_remote_description(new SessionDescription(accept.ClearContents()));
+ SetState(STATE_RECEIVEDACCEPT);
+
+ // Users of Session may listen to state change and call Reject().
+ if (state_ != STATE_SENTREJECT) {
+ if (!OnRemoteCandidates(accept.transports, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool Session::OnRejectMessage(const SessionMessage& msg, MessageError* error) {
+ if (!CheckState(STATE_SENTINITIATE, error))
+ return false;
+
+ SetState(STATE_RECEIVEDREJECT);
+ return true;
+}
+
+// Only used by app/win32/fileshare.cc.
+bool Session::OnInfoMessage(const SessionMessage& msg) {
+ SignalInfoMessage(this, CopyOfXmlChildren(msg.action_elem));
+ return true;
+}
+
+bool Session::OnTerminateMessage(const SessionMessage& msg,
+ MessageError* error) {
+ SessionTerminate term;
+ if (!ParseSessionTerminate(msg.protocol, msg.action_elem, &term, error))
+ return false;
+
+ SignalReceivedTerminateReason(this, term.reason);
+ if (term.debug_reason != buzz::STR_EMPTY) {
+ LOG(LS_VERBOSE) << "Received error on call: " << term.debug_reason;
+ }
+
+ SetState(STATE_RECEIVEDTERMINATE);
+ return true;
+}
+
+bool Session::OnTransportInfoMessage(const SessionMessage& msg,
+ MessageError* error) {
+ TransportInfos tinfos;
+ if (!ParseTransportInfos(msg.protocol, msg.action_elem,
+ initiator_description()->contents(),
+ GetTransportParsers(), &tinfos, error))
+ return false;
+
+ if (!OnRemoteCandidates(tinfos, error))
+ return false;
+
+ return true;
+}
+
+bool Session::OnTransportAcceptMessage(const SessionMessage& msg,
+ MessageError* error) {
+ // TODO: Currently here only for compatibility with
+ // Gingle 1.1 clients (notably, Google Voice).
+ return true;
+}
+
+bool Session::OnNotifyMessage(const SessionMessage& msg,
+ MessageError* error) {
+ SessionNotify notify;
+ if (!ParseSessionNotify(msg.action_elem, ¬ify, error)) {
+ return false;
+ }
+
+ SignalMediaSources(notify.nickname_to_sources);
+
+ return true;
+}
+
+bool Session::OnUpdateMessage(const SessionMessage& msg,
+ MessageError* error) {
+ SessionUpdate update;
+ if (!ParseSessionUpdate(msg.action_elem, &update, error)) {
+ return false;
+ }
+
+ // TODO: Process this message appropriately.
+
+ return true;
+}
+
+bool BareJidsEqual(const std::string& name1,
+ const std::string& name2) {
+ buzz::Jid jid1(name1);
+ buzz::Jid jid2(name2);
+
+ return jid1.IsValid() && jid2.IsValid() && jid1.BareEquals(jid2);
+}
+
+bool Session::OnRedirectError(const SessionRedirect& redirect,
+ SessionError* error) {
+ MessageError message_error;
+ if (!CheckState(STATE_SENTINITIATE, &message_error)) {
+ return BadWrite(message_error.text, error);
+ }
+
+ if (!BareJidsEqual(remote_name_, redirect.target))
+ return BadWrite("Redirection not allowed: must be the same bare jid.",
+ error);
+
+ // When we receive a redirect, we point the session at the new JID
+ // and resend the candidates.
+ remote_name_ = redirect.target;
+ return (SendInitiateMessage(local_description(), error) &&
+ ResendAllTransportInfoMessages(error));
+}
+
+bool Session::CheckState(State state, MessageError* error) {
+ ASSERT(state_ == state);
+ if (state_ != state) {
+ return BadMessage(buzz::QN_STANZA_NOT_ALLOWED,
+ "message not allowed in current state",
+ error);
+ }
+ return true;
+}
+
+void Session::SetError(Error error) {
+ BaseSession::SetError(error);
+ if (error_ != ERROR_NONE)
+ signaling_thread_->Post(this, MSG_ERROR);
+}
+
+void Session::OnMessage(talk_base::Message *pmsg) {
+ // preserve this because BaseSession::OnMessage may modify it
+ BaseSession::State orig_state = state_;
+
+ BaseSession::OnMessage(pmsg);
+
+ switch (pmsg->message_id) {
+ case MSG_STATE:
+ switch (orig_state) {
+ case STATE_SENTTERMINATE:
+ case STATE_RECEIVEDTERMINATE:
+ session_manager_->DestroySession(this);
+ break;
+
+ default:
+ // Explicitly ignoring some states here.
+ break;
+ }
+ break;
+ }
+}
+
+bool Session::SendInitiateMessage(const SessionDescription* sdesc,
+ SessionError* error) {
+ SessionInitiate init;
+ init.contents = sdesc->contents();
+ init.transports = GetEmptyTransportInfos(init.contents);
+ return SendMessage(ACTION_SESSION_INITIATE, init, error);
+}
+
+bool Session::WriteSessionAction(
+ SignalingProtocol protocol, const SessionInitiate& init,
+ XmlElements* elems, WriteError* error) {
+ ContentParserMap content_parsers = GetContentParsers();
+ TransportParserMap trans_parsers = GetTransportParsers();
+
+ return WriteSessionInitiate(protocol, init.contents, init.transports,
+ content_parsers, trans_parsers,
+ elems, error);
+}
+
+bool Session::SetVideoView(
+ const std::vector<VideoViewRequest>& view_requests) {
+ SessionView view;
+ SessionError error;
+
+ view.view_requests = view_requests;
+
+ return !SendViewMessage(view, &error);
+}
+
+bool Session::SendAcceptMessage(const SessionDescription* sdesc,
+ SessionError* error) {
+ XmlElements elems;
+ if (!WriteSessionAccept(current_protocol_,
+ sdesc->contents(),
+ GetEmptyTransportInfos(sdesc->contents()),
+ GetContentParsers(), GetTransportParsers(),
+ &elems, error)) {
+ return false;
+ }
+ return SendMessage(ACTION_SESSION_ACCEPT, elems, error);
+}
+
+bool Session::SendRejectMessage(const std::string& reason,
+ SessionError* error) {
+ XmlElements elems;
+ return SendMessage(ACTION_SESSION_REJECT, elems, error);
+}
+
+
+bool Session::SendTerminateMessage(const std::string& reason,
+ SessionError* error) {
+ SessionTerminate term(reason);
+ return SendMessage(ACTION_SESSION_TERMINATE, term, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems, WriteError* error) {
+ WriteSessionTerminate(protocol, term, elems);
+ return true;
+}
+
+bool Session::SendTransportInfoMessage(const TransportInfo& tinfo,
+ SessionError* error) {
+ return SendMessage(ACTION_TRANSPORT_INFO, tinfo, error);
+}
+
+bool Session::WriteSessionAction(SignalingProtocol protocol,
+ const TransportInfo& tinfo,
+ XmlElements* elems, WriteError* error) {
+ TransportInfos tinfos;
+ tinfos.push_back(tinfo);
+ TransportParserMap parsers = GetTransportParsers();
+
+ return WriteTransportInfos(protocol, tinfos, parsers,
+ elems, error);
+}
+
+bool Session::SendViewMessage(const SessionView& view, SessionError* error) {
+ XmlElements elems;
+ WriteSessionView(view, &elems);
+ return SendMessage(ACTION_VIEW, elems, error);
+}
+
+bool Session::ResendAllTransportInfoMessages(SessionError* error) {
+ for (TransportMap::iterator iter = transports_.begin();
+ iter != transports_.end(); ++iter) {
+ TransportProxy* transproxy = iter->second;
+ if (transproxy->sent_candidates().size() > 0) {
+ if (!SendTransportInfoMessage(
+ TransportInfo(
+ transproxy->content_name(),
+ transproxy->type(),
+ transproxy->sent_candidates()),
+ error)) {
+ return false;
+ }
+ transproxy->ClearSentCandidates();
+ }
+ }
+ return true;
+}
+
+bool Session::SendMessage(ActionType type, const XmlElements& action_elems,
+ SessionError* error) {
+ talk_base::scoped_ptr<buzz::XmlElement> stanza(
+ new buzz::XmlElement(buzz::QN_IQ));
+
+ SessionMessage msg(current_protocol_, type, sid_, initiator_name_);
+ msg.to = remote_name_;
+ WriteSessionMessage(msg, action_elems, stanza.get());
+
+ SignalOutgoingMessage(this, stanza.get());
+ return true;
+}
+
+template <typename Action>
+bool Session::SendMessage(ActionType type, const Action& action,
+ SessionError* error) {
+ talk_base::scoped_ptr<buzz::XmlElement> stanza(
+ new buzz::XmlElement(buzz::QN_IQ));
+ if (!WriteActionMessage(type, action, stanza.get(), error))
+ return false;
+
+ SignalOutgoingMessage(this, stanza.get());
+ return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(ActionType type, const Action& action,
+ buzz::XmlElement* stanza,
+ WriteError* error) {
+ if (current_protocol_ == PROTOCOL_HYBRID) {
+ if (!WriteActionMessage(PROTOCOL_JINGLE, type, action, stanza, error))
+ return false;
+ if (!WriteActionMessage(PROTOCOL_GINGLE, type, action, stanza, error))
+ return false;
+ } else {
+ if (!WriteActionMessage(current_protocol_, type, action, stanza, error))
+ return false;
+ }
+ return true;
+}
+
+template <typename Action>
+bool Session::WriteActionMessage(SignalingProtocol protocol,
+ ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error) {
+ XmlElements action_elems;
+ if (!WriteSessionAction(protocol, action, &action_elems, error))
+ return false;
+
+ SessionMessage msg(protocol, type, sid_, initiator_name_);
+ msg.to = remote_name_;
+
+ WriteSessionMessage(msg, action_elems, stanza);
+ return true;
+}
+
+void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) {
+ talk_base::scoped_ptr<buzz::XmlElement> ack(
+ new buzz::XmlElement(buzz::QN_IQ));
+ ack->SetAttr(buzz::QN_TO, remote_name_);
+ ack->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+ ack->SetAttr(buzz::QN_TYPE, "result");
+
+ SignalOutgoingMessage(this, ack.get());
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/session.h b/talk/p2p/base/session.h
new file mode 100644
index 0000000..b00e809
--- /dev/null
+++ b/talk/p2p/base/session.h
@@ -0,0 +1,551 @@
+/*
+ * 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_P2P_BASE_SESSION_H_
+#define TALK_P2P_BASE_SESSION_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <vector>
+
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/base/socketaddress.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/port.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class P2PTransportChannel;
+class Transport;
+class TransportChannel;
+class TransportChannelProxy;
+class TransportChannelImpl;
+
+// Used for errors that will send back a specific error message to the
+// remote peer. We add "type" to the errors because it's needed for
+// SignalErrorMessage.
+struct MessageError : ParseError {
+ buzz::QName type;
+
+ // if unset, assume type is a parse error
+ MessageError() : ParseError(), type(buzz::QN_STANZA_BAD_REQUEST) {}
+
+ void SetType(const buzz::QName type) {
+ this->type = type;
+ }
+};
+
+// Used for errors that may be returned by public session methods that
+// can fail.
+// TODO: Use this error in Session::Initiate and
+// Session::Accept.
+struct SessionError : WriteError {
+};
+
+// Bundles a Transport and ChannelMap together. ChannelMap is used to
+// create transport channels before receiving or sending a session
+// initiate, and for speculatively connecting channels. Previously, a
+// session had one ChannelMap and transport. Now, with multiple
+// transports per session, we need multiple ChannelMaps as well.
+class TransportProxy {
+ public:
+ TransportProxy(const std::string& content_name, Transport* transport)
+ : content_name_(content_name),
+ transport_(transport),
+ state_(STATE_INIT),
+ sent_candidates_(false) {}
+ ~TransportProxy();
+
+ std::string content_name() const { return content_name_; }
+ Transport* impl() const { return transport_; }
+ std::string type() const;
+ bool negotiated() const { return state_ == STATE_NEGOTIATED; }
+ const Candidates& sent_candidates() const { return sent_candidates_; }
+
+ TransportChannel* GetChannel(const std::string& name);
+ TransportChannel* CreateChannel(const std::string& name,
+ const std::string& content_type);
+ void DestroyChannel(const std::string& name);
+ void AddSentCandidates(const Candidates& candidates);
+ void ClearSentCandidates() { sent_candidates_.clear(); }
+ void SpeculativelyConnectChannels();
+ void CompleteNegotiation();
+
+ private:
+ enum TransportState {
+ STATE_INIT,
+ STATE_CONNECTING,
+ STATE_NEGOTIATED
+ };
+
+ typedef std::map<std::string, TransportChannelProxy*> ChannelMap;
+
+ TransportChannelProxy* GetProxy(const std::string& name);
+ TransportChannelImpl* GetOrCreateImpl(const std::string& name,
+ const std::string& content_type);
+ void SetProxyImpl(const std::string& name, TransportChannelProxy* proxy);
+
+ std::string content_name_;
+ Transport* transport_;
+ TransportState state_;
+ ChannelMap channels_;
+ Candidates sent_candidates_;
+};
+
+typedef std::map<std::string, TransportProxy*> TransportMap;
+
+// TODO: Consider simplifying the dependency from Voice/VideoChannel
+// on Session. Right now the Channel class requires a BaseSession, but it only
+// uses CreateChannel/DestroyChannel. Perhaps something like a
+// TransportChannelFactory could be hoisted up out of BaseSession, or maybe
+// the transports could be passed in directly.
+
+// A BaseSession manages general session state. This includes negotiation
+// of both the application-level and network-level protocols: the former
+// defines what will be sent and the latter defines how it will be sent. Each
+// network-level protocol is represented by a Transport object. Each Transport
+// participates in the network-level negotiation. The individual streams of
+// packets are represented by TransportChannels. The application-level protocol
+// is represented by SessionDecription objects.
+class BaseSession : public sigslot::has_slots<>,
+ public talk_base::MessageHandler {
+ public:
+ enum State {
+ STATE_INIT = 0,
+ STATE_SENTINITIATE, // sent initiate, waiting for Accept or Reject
+ STATE_RECEIVEDINITIATE, // received an initiate. Call Accept or Reject
+ STATE_SENTACCEPT, // sent accept. begin connecting transport
+ STATE_RECEIVEDACCEPT, // received accept. begin connecting transport
+ STATE_SENTMODIFY, // sent modify, waiting for Accept or Reject
+ STATE_RECEIVEDMODIFY, // received modify, call Accept or Reject
+ STATE_SENTREJECT, // sent reject after receiving initiate
+ STATE_RECEIVEDREJECT, // received reject after sending initiate
+ STATE_SENTREDIRECT, // sent direct after receiving initiate
+ STATE_SENTTERMINATE, // sent terminate (any time / either side)
+ STATE_RECEIVEDTERMINATE, // received terminate (any time / either side)
+ STATE_INPROGRESS, // session accepted and in progress
+ STATE_DEINIT, // session is being destroyed
+ };
+
+ enum Error {
+ ERROR_NONE = 0, // no error
+ ERROR_TIME = 1, // no response to signaling
+ ERROR_RESPONSE = 2, // error during signaling
+ ERROR_NETWORK = 3, // network error, could not allocate network resources
+ ERROR_CONTENT = 4, // channel errors in SetLocalContent/SetRemoteContent
+ };
+
+ explicit BaseSession(talk_base::Thread *signaling_thread);
+ virtual ~BaseSession();
+
+ // Updates the state, signaling if necessary.
+ void SetState(State state);
+
+ // Updates the error state, signaling if necessary.
+ virtual void SetError(Error error);
+
+ // Handles messages posted to us.
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ // Returns the current state of the session. See the enum above for details.
+ // Each time the state changes, we will fire this signal.
+ State state() const { return state_; }
+ sigslot::signal2<BaseSession *, State> SignalState;
+
+ // Returns the last error in the session. See the enum above for details.
+ // Each time the an error occurs, we will fire this signal.
+ Error error() const { return error_; }
+ sigslot::signal2<BaseSession *, Error> SignalError;
+
+ // Creates a new channel with the given names. This method may be called
+ // immediately after creating the session. However, the actual
+ // implementation may not be fixed until transport negotiation completes.
+ // This will usually be called from the worker thread, but that
+ // shouldn't be an issue since the main thread will be blocked in
+ // Send when doing so.
+ virtual TransportChannel* CreateChannel(const std::string& content_name,
+ const std::string& channel_name) = 0;
+
+ // Returns the channel with the given names.
+ virtual TransportChannel* GetChannel(const std::string& content_name,
+ const std::string& channel_name) = 0;
+
+ // Destroys the channel with the given names.
+ // This will usually be called from the worker thread, but that
+ // shouldn't be an issue since the main thread will be blocked in
+ // Send when doing so.
+ virtual void DestroyChannel(const std::string& content_name,
+ const std::string& channel_name) = 0;
+
+ // Invoked when we notice that there is no matching channel on our peer.
+ sigslot::signal2<Session*, const std::string&> SignalChannelGone;
+
+ // Returns the application-level description given by our client.
+ // If we are the recipient, this will be NULL until we send an accept.
+ const SessionDescription* local_description() const {
+ return local_description_;
+ }
+ // Takes ownership of SessionDescription*
+ bool set_local_description(const SessionDescription* sdesc) {
+ if (sdesc != local_description_) {
+ delete local_description_;
+ local_description_ = sdesc;
+ }
+ return true;
+ }
+
+ // Returns the application-level description given by the other client.
+ // If we are the initiator, this will be NULL until we receive an accept.
+ const SessionDescription* remote_description() const {
+ return remote_description_;
+ }
+ // Takes ownership of SessionDescription*
+ bool set_remote_description(const SessionDescription* sdesc) {
+ if (sdesc != remote_description_) {
+ delete remote_description_;
+ remote_description_ = sdesc;
+ }
+ return true;
+ }
+
+ // When we receive an initiate, we create a session in the
+ // RECEIVEDINITIATE state and respond by accepting or rejecting.
+ // Takes ownership of session description.
+ virtual bool Accept(const SessionDescription* sdesc) = 0;
+ virtual bool Reject(const std::string& reason) = 0;
+ bool Terminate() {
+ return TerminateWithReason(STR_TERMINATE_SUCCESS);
+ }
+ virtual bool TerminateWithReason(const std::string& reason) = 0;
+
+ // The worker thread used by the session manager
+ virtual talk_base::Thread *worker_thread() = 0;
+
+ talk_base::Thread *signaling_thread() {
+ return signaling_thread_;
+ }
+
+ // Returns the JID of this client.
+ const std::string& local_name() const { return local_name_; }
+
+ // Returns the JID of the other peer in this session.
+ const std::string& remote_name() const { return remote_name_; }
+
+ // Set the JID of the other peer in this session.
+ // Typically the remote_name_ is set when the session is initiated.
+ // However, sometimes (e.g when a proxy is used) the peer name is
+ // known after the BaseSession has been initiated and it must be updated
+ // explicitly.
+ void set_remote_name(const std::string& name) { remote_name_ = name; }
+
+ const std::string& id() const { return sid_; }
+
+ protected:
+ State state_;
+ Error error_;
+ const SessionDescription* local_description_;
+ const SessionDescription* remote_description_;
+ std::string sid_;
+ // We don't use buzz::Jid because changing to buzz:Jid here has a
+ // cascading effect that requires an enormous number places to
+ // change to buzz::Jid as well.
+ std::string local_name_;
+ std::string remote_name_;
+ talk_base::Thread *signaling_thread_;
+};
+
+// A specific Session created by the SessionManager, using XMPP for protocol.
+class Session : public BaseSession {
+ public:
+ // Returns the manager that created and owns this session.
+ SessionManager* session_manager() const { return session_manager_; }
+
+ // the worker thread used by the session manager
+ talk_base::Thread *worker_thread() {
+ return session_manager_->worker_thread();
+ }
+
+ // Returns the XML namespace identifying the type of this session.
+ const std::string& content_type() const { return content_type_; }
+
+ // Returns the client that is handling the application data of this session.
+ SessionClient* client() const { return client_; }
+
+ SignalingProtocol current_protocol() const { return current_protocol_; }
+
+ void set_current_protocol(SignalingProtocol protocol) {
+ current_protocol_ = protocol;
+ }
+
+ // Indicates whether we initiated this session.
+ bool initiator() const { return initiator_; }
+
+ const SessionDescription* initiator_description() const {
+ if (initiator_) {
+ return local_description_;
+ } else {
+ return remote_description_;
+ }
+ }
+
+ // Fired whenever we receive a terminate message along with a reason
+ sigslot::signal2<Session*, const std::string&> SignalReceivedTerminateReason;
+
+ void set_allow_local_ips(bool allow);
+
+ // Returns the transport that has been negotiated or NULL if
+ // negotiation is still in progress.
+ Transport* GetTransport(const std::string& content_name);
+
+ // Takes ownership of session description.
+ // TODO: Add an error argument to pass back to the caller.
+ bool Initiate(const std::string& to,
+ const SessionDescription* sdesc);
+
+ // When we receive an initiate, we create a session in the
+ // RECEIVEDINITIATE state and respond by accepting or rejecting.
+ // Takes ownership of session description.
+ // TODO: Add an error argument to pass back to the caller.
+ virtual bool Accept(const SessionDescription* sdesc);
+ virtual bool Reject(const std::string& reason);
+ virtual bool TerminateWithReason(const std::string& reason);
+
+ // The two clients in the session may also send one another arbitrary XML
+ // messages, which are called "info" messages. Both of these functions take
+ // ownership of the XmlElements and delete them when done.
+ bool SendInfoMessage(const XmlElements& elems);
+ sigslot::signal2<Session*, const XmlElements&> SignalInfoMessage;
+
+ // Maps passed to serialization functions.
+ TransportParserMap GetTransportParsers();
+ ContentParserMap GetContentParsers();
+
+ // Creates a new channel with the given names. This method may be called
+ // immediately after creating the session. However, the actual
+ // implementation may not be fixed until transport negotiation completes.
+ virtual TransportChannel* CreateChannel(const std::string& content_name,
+ const std::string& channel_name);
+
+ // Returns the channel with the given names.
+ virtual TransportChannel* GetChannel(const std::string& content_name,
+ const std::string& channel_name);
+
+ // Destroys the channel with the given names.
+ virtual void DestroyChannel(const std::string& content_name,
+ const std::string& channel_name);
+
+ // Updates the error state, signaling if necessary.
+ virtual void SetError(Error error);
+
+ // Handles messages posted to us.
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ // Fired when notification of media sources is received from the server.
+ // Passes a map whose keys are strings containing nick names for users
+ // in the session and whose values contain the SSRCs for each user.
+ sigslot::signal1<const StringToMediaSourcesMap&> SignalMediaSources;
+
+ // Sets the video streams to receive from the server.
+ bool SetVideoView(const VideoViewRequestVector& view_requests);
+
+ private:
+ // Creates or destroys a session. (These are called only SessionManager.)
+ Session(SessionManager *session_manager,
+ const std::string& local_name, const std::string& initiator_name,
+ const std::string& sid, const std::string& content_type,
+ SessionClient* client);
+ ~Session();
+
+ // Get a TransportProxy by content_name or transport. NULL if not found.
+ TransportProxy* GetTransportProxy(const std::string& content_name);
+ TransportProxy* GetTransportProxy(const Transport* transport);
+ TransportProxy* GetFirstTransportProxy();
+ // TransportProxy is owned by session. Return proxy just for convenience.
+ TransportProxy* GetOrCreateTransportProxy(const std::string& content_name);
+ // For each transport info, create a transport proxy. Can fail for
+ // incompatible transport types.
+ bool CreateTransportProxies(const TransportInfos& tinfos,
+ SessionError* error);
+ void SpeculativelyConnectAllTransportChannels();
+ bool OnRemoteCandidates(const TransportInfos& tinfos,
+ ParseError* error);
+ // Returns a TransportInfo without candidates for each content name.
+ // Uses the transport_type_ of the session.
+ TransportInfos GetEmptyTransportInfos(const ContentInfos& contents) const;
+
+ // Called when the first channel of a transport begins connecting. We use
+ // this to start a timer, to make sure that the connection completes in a
+ // reasonable amount of time.
+ void OnTransportConnecting(Transport* transport);
+
+ // Called when a transport changes its writable state. We track this to make
+ // sure that the transport becomes writable within a reasonable amount of
+ // time. If this does not occur, we signal an error.
+ void OnTransportWritable(Transport* transport);
+
+ // Called when a transport requests signaling.
+ void OnTransportRequestSignaling(Transport* transport);
+
+ // Called when a transport signals that it has a message to send. Note that
+ // these messages are just the transport part of the stanza; they need to be
+ // wrapped in the appropriate session tags.
+ void OnTransportCandidatesReady(Transport* transport,
+ const Candidates& candidates);
+
+ // Called when a transport signals that it found an error in an incoming
+ // message.
+ void OnTransportSendError(Transport* transport,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ // Called when we notice that one of our local channels has no peer, so it
+ // should be destroyed.
+ void OnTransportChannelGone(Transport* transport, const std::string& name);
+
+ // When the session needs to send signaling messages, it beings by requesting
+ // signaling. The client should handle this by calling OnSignalingReady once
+ // it is ready to send the messages.
+ // (These are called only by SessionManager.)
+ sigslot::signal1<Session*> SignalRequestSignaling;
+ void OnSignalingReady();
+
+ // Send various kinds of session messages.
+ bool SendInitiateMessage(const SessionDescription* sdesc,
+ SessionError* error);
+ bool SendAcceptMessage(const SessionDescription* sdesc, SessionError* error);
+ bool SendRejectMessage(const std::string& reason, SessionError* error);
+ bool SendTerminateMessage(const std::string& reason, SessionError* error);
+ bool SendTransportInfoMessage(const TransportInfo& tinfo,
+ SessionError* error);
+ bool SendViewMessage(const SessionView& view, SessionError* error);
+ bool ResendAllTransportInfoMessages(SessionError* error);
+
+ // Both versions of SendMessage send a message of the given type to
+ // the other client. Can pass either a set of elements or an
+ // "action", which must have a WriteSessionAction method to go along
+ // with it. Sending with an action supports sending a "hybrid"
+ // message. Sending with elements must be sent as Jingle or Gingle.
+
+ // When passing elems, must be either Jingle or Gingle protocol.
+ // Takes ownership of action_elems.
+ bool SendMessage(ActionType type, const XmlElements& action_elems,
+ SessionError* error);
+ // When passing an action, may be Hybrid protocol.
+ template <typename Action>
+ bool SendMessage(ActionType type, const Action& action,
+ SessionError* error);
+
+ // Helper methods to write the session message stanza.
+ template <typename Action>
+ bool WriteActionMessage(ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error);
+ template <typename Action>
+ bool WriteActionMessage(SignalingProtocol protocol,
+ ActionType type, const Action& action,
+ buzz::XmlElement* stanza, WriteError* error);
+
+ // Sending messages in hybrid form requires being able to write them
+ // on a per-protocol basis with a common method signature, which all
+ // of these have.
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const SessionInitiate& init,
+ XmlElements* elems, WriteError* error);
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const TransportInfo& tinfo,
+ XmlElements* elems, WriteError* error);
+ bool WriteSessionAction(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems, WriteError* error);
+
+ // Sends a message back to the other client indicating that we have received
+ // and accepted their message.
+ void SendAcknowledgementMessage(const buzz::XmlElement* stanza);
+
+ // Once signaling is ready, the session will use this signal to request the
+ // sending of each message. When messages are received by the other client,
+ // they should be handed to OnIncomingMessage.
+ // (These are called only by SessionManager.)
+ sigslot::signal2<Session *, const buzz::XmlElement*> SignalOutgoingMessage;
+ void OnIncomingMessage(const SessionMessage& msg);
+
+ void OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza);
+
+ // Invoked when an error is found in an incoming message. This is translated
+ // into the appropriate XMPP response by SessionManager.
+ sigslot::signal6<BaseSession*,
+ const buzz::XmlElement*,
+ const buzz::QName&,
+ const std::string&,
+ const std::string&,
+ const buzz::XmlElement*> SignalErrorMessage;
+
+ // Handlers for the various types of messages. These functions may take
+ // pointers to the whole stanza or to just the session element.
+ bool OnInitiateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnAcceptMessage(const SessionMessage& msg, MessageError* error);
+ bool OnRejectMessage(const SessionMessage& msg, MessageError* error);
+ bool OnInfoMessage(const SessionMessage& msg);
+ bool OnTerminateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnTransportInfoMessage(const SessionMessage& msg, MessageError* error);
+ bool OnTransportAcceptMessage(const SessionMessage& msg, MessageError* error);
+ bool OnNotifyMessage(const SessionMessage& msg, MessageError* error);
+ bool OnUpdateMessage(const SessionMessage& msg, MessageError* error);
+ bool OnRedirectError(const SessionRedirect& redirect, SessionError* error);
+
+ // Verifies that we are in the appropriate state to receive this message.
+ bool CheckState(State state, MessageError* error);
+
+ SessionManager *session_manager_;
+ bool initiator_;
+ std::string initiator_name_;
+ std::string content_type_;
+ SessionClient* client_;
+ std::string transport_type_;
+ TransportParser* transport_parser_;
+ // This is transport-specific but required so much by unit tests
+ // that it's much easier to put it here.
+ bool allow_local_ips_;
+ TransportMap transports_;
+ // Keeps track of what protocol we are speaking.
+ SignalingProtocol current_protocol_;
+
+ friend class SessionManager; // For access to constructor, destructor,
+ // and signaling related methods.
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSION_H_
diff --git a/talk/p2p/base/sessionclient.h b/talk/p2p/base/sessionclient.h
new file mode 100644
index 0000000..d6604a9
--- /dev/null
+++ b/talk/p2p/base/sessionclient.h
@@ -0,0 +1,85 @@
+/*
+ * 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_P2P_BASE_SESSIONCLIENT_H_
+#define TALK_P2P_BASE_SESSIONCLIENT_H_
+
+namespace buzz {
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+class Session;
+class SessionDescription;
+
+class ContentParser {
+ public:
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error) = 0;
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error) = 0;
+ virtual ~ContentParser() {}
+};
+
+// A SessionClient exists in 1-1 relation with each session. The implementor
+// of this interface is the one that understands *what* the two sides are
+// trying to send to one another. The lower-level layers only know how to send
+// data; they do not know what is being sent.
+class SessionClient : public ContentParser {
+ public:
+ // Notifies the client of the creation / destruction of sessions of this type.
+ //
+ // IMPORTANT: The SessionClient, in its handling of OnSessionCreate, must
+ // create whatever channels are indicate in the description. This is because
+ // the remote client may already be attempting to connect those channels. If
+ // we do not create our channel right away, then connection may fail or be
+ // delayed.
+ virtual void OnSessionCreate(Session* session, bool received_initiate) = 0;
+ virtual void OnSessionDestroy(Session* session) = 0;
+
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error) = 0;
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error) = 0;
+ protected:
+ // The SessionClient interface explicitly does not include destructor
+ virtual ~SessionClient() { }
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSIONCLIENT_H_
diff --git a/talk/p2p/base/sessiondescription.cc b/talk/p2p/base/sessiondescription.cc
new file mode 100644
index 0000000..872358e
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.cc
@@ -0,0 +1,72 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/p2p/base/sessiondescription.h"
+
+#include "talk/xmllite/xmlelement.h"
+
+namespace cricket {
+
+const ContentInfo* FindContentInfoByName(
+ const ContentInfos& contents, const std::string& name) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); content++) {
+ if (content->name == name) {
+ return &(*content);
+ }
+ }
+ return NULL;
+}
+
+const ContentInfo* FindContentInfoByType(
+ const ContentInfos& contents, const std::string& type) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); content++) {
+ if (content->type == type) {
+ return &(*content);
+ }
+ }
+ return NULL;
+}
+
+const ContentInfo* SessionDescription::GetContentByName(
+ const std::string& name) const {
+ return FindContentInfoByName(contents_, name);
+}
+
+const ContentInfo* SessionDescription::FirstContentByType(
+ const std::string& type) const {
+ return FindContentInfoByType(contents_, type);
+}
+
+void SessionDescription::AddContent(const std::string& name,
+ const std::string& type,
+ const ContentDescription* description) {
+ contents_.push_back(ContentInfo(name, type, description));
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/sessiondescription.h b/talk/p2p/base/sessiondescription.h
new file mode 100644
index 0000000..fe575fa
--- /dev/null
+++ b/talk/p2p/base/sessiondescription.h
@@ -0,0 +1,110 @@
+/*
+ * 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_P2P_BASE_SESSIONDESCRIPTION_H_
+#define TALK_P2P_BASE_SESSIONDESCRIPTION_H_
+
+#include <string>
+#include <vector>
+
+namespace cricket {
+
+// Describes a session content. Individual content types inherit from
+// this class. Analagous to a <jingle><content><description> or
+// <session><description>.
+class ContentDescription {
+ public:
+ virtual ~ContentDescription() {}
+};
+
+// Analagous to a <jingle><content> or <session><description>.
+// name = name of <content name="...">
+// type = xmlns of <content>
+struct ContentInfo {
+ ContentInfo() : description(NULL) {}
+ ContentInfo(const std::string& name,
+ const std::string& type,
+ const ContentDescription* description) :
+ name(name), type(type), description(description) {}
+ std::string name;
+ std::string type;
+ const ContentDescription* description;
+};
+
+typedef std::vector<ContentInfo> ContentInfos;
+const ContentInfo* FindContentInfoByName(
+ const ContentInfos& contents, const std::string& name);
+const ContentInfo* FindContentInfoByType(
+ const ContentInfos& contents, const std::string& type);
+
+// Describes a collection of contents, each with its own name and
+// type. Analgous to a <jingle> or <session> stanza. Assumes that
+// contents are unique be name, but doesn't enforce that.
+class SessionDescription {
+ public:
+ SessionDescription() {}
+ explicit SessionDescription(const ContentInfos& contents) :
+ contents_(contents) {}
+ const ContentInfo* GetContentByName(const std::string& name) const;
+ const ContentInfo* FirstContentByType(const std::string& type) const;
+ // Takes ownership of ContentDescription*.
+ void AddContent(const std::string& name,
+ const std::string& type,
+ const ContentDescription* description);
+ // TODO: Implement RemoveContent when it's needed for
+ // content-remove Jingle messages.
+ // void RemoveContent(const std::string& name);
+ const ContentInfos& contents() const { return contents_; }
+
+ ~SessionDescription() {
+ for (ContentInfos::iterator content = contents_.begin();
+ content != contents_.end(); content++) {
+ delete content->description;
+ }
+ }
+
+ private:
+ ContentInfos contents_;
+};
+
+// Indicates whether a ContentDescription was an offer or an answer, as
+// described in http://www.ietf.org/rfc/rfc3264.txt. CA_UPDATE
+// indicates a jingle update message which contains a subset of a full
+// session description
+enum ContentAction {
+ CA_OFFER, CA_ANSWER, CA_UPDATE
+};
+
+// Indicates whether a ContentDescription was sent by the local client
+// or received from the remote client.
+enum ContentSource {
+ CS_LOCAL, CS_REMOTE
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSIONDESCRIPTION_H_
diff --git a/talk/p2p/base/sessionid.h b/talk/p2p/base/sessionid.h
new file mode 100644
index 0000000..6942942
--- /dev/null
+++ b/talk/p2p/base/sessionid.h
@@ -0,0 +1,37 @@
+/*
+ * 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_P2P_BASE_SESSIONID_H_
+#define TALK_P2P_BASE_SESSIONID_H_
+
+// TODO: Remove this file.
+
+namespace cricket {
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSIONID_H_
diff --git a/talk/p2p/base/sessionmanager.cc b/talk/p2p/base/sessionmanager.cc
new file mode 100644
index 0000000..61a4c4a
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.cc
@@ -0,0 +1,296 @@
+/*
+ * 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 "talk/p2p/base/sessionmanager.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionmessages.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+
+namespace cricket {
+
+SessionManager::SessionManager(PortAllocator *allocator,
+ talk_base::Thread *worker) {
+ allocator_ = allocator;
+ signaling_thread_ = talk_base::Thread::Current();
+ if (worker == NULL) {
+ worker_thread_ = talk_base::Thread::Current();
+ } else {
+ worker_thread_ = worker;
+ }
+ timeout_ = 50;
+}
+
+SessionManager::~SessionManager() {
+ // Note: Session::Terminate occurs asynchronously, so it's too late to
+ // delete them now. They better be all gone.
+ ASSERT(session_map_.empty());
+ // TerminateAll();
+}
+
+void SessionManager::AddClient(const std::string& content_type,
+ SessionClient* client) {
+ ASSERT(client_map_.find(content_type) == client_map_.end());
+ client_map_[content_type] = client;
+}
+
+void SessionManager::RemoveClient(const std::string& content_type) {
+ ClientMap::iterator iter = client_map_.find(content_type);
+ ASSERT(iter != client_map_.end());
+ client_map_.erase(iter);
+}
+
+SessionClient* SessionManager::GetClient(const std::string& content_type) {
+ ClientMap::iterator iter = client_map_.find(content_type);
+ return (iter != client_map_.end()) ? iter->second : NULL;
+}
+
+Session* SessionManager::CreateSession(const std::string& local_name,
+ const std::string& content_type) {
+ return CreateSession(local_name, local_name,
+ talk_base::ToString(talk_base::CreateRandomId()),
+ content_type, false);
+}
+
+Session* SessionManager::CreateSession(
+ const std::string& local_name, const std::string& initiator_name,
+ const std::string& sid, const std::string& content_type,
+ bool received_initiate) {
+ SessionClient* client = GetClient(content_type);
+ ASSERT(client != NULL);
+
+ Session* session = new Session(this, local_name, initiator_name,
+ sid, content_type, client);
+ session_map_[session->id()] = session;
+ session->SignalRequestSignaling.connect(
+ this, &SessionManager::OnRequestSignaling);
+ session->SignalOutgoingMessage.connect(
+ this, &SessionManager::OnOutgoingMessage);
+ session->SignalErrorMessage.connect(this, &SessionManager::OnErrorMessage);
+ SignalSessionCreate(session, received_initiate);
+ session->client()->OnSessionCreate(session, received_initiate);
+ return session;
+}
+
+void SessionManager::DestroySession(Session* session) {
+ if (session != NULL) {
+ SessionMap::iterator it = session_map_.find(session->id());
+ if (it != session_map_.end()) {
+ SignalSessionDestroy(session);
+ session->client()->OnSessionDestroy(session);
+ session_map_.erase(it);
+ delete session;
+ }
+ }
+}
+
+Session* SessionManager::GetSession(const std::string& sid) {
+ SessionMap::iterator it = session_map_.find(sid);
+ if (it != session_map_.end())
+ return it->second;
+ return NULL;
+}
+
+void SessionManager::TerminateAll() {
+ while (session_map_.begin() != session_map_.end()) {
+ Session* session = session_map_.begin()->second;
+ session->Terminate();
+ }
+}
+
+bool SessionManager::IsSessionMessage(const buzz::XmlElement* stanza) {
+ return cricket::IsSessionMessage(stanza);
+}
+
+Session* SessionManager::FindSession(const std::string& sid,
+ const std::string& remote_name) {
+ SessionMap::iterator iter = session_map_.find(sid);
+ if (iter == session_map_.end())
+ return NULL;
+
+ Session* session = iter->second;
+ if (buzz::Jid(remote_name) != buzz::Jid(session->remote_name()))
+ return NULL;
+
+ return session;
+}
+
+void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) {
+ SessionMessage msg;
+ ParseError error;
+
+ if (!ParseSessionMessage(stanza, &msg, &error)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ error.text, NULL);
+ return;
+ }
+
+ Session* session = FindSession(msg.sid, msg.from);
+ if (session) {
+ session->OnIncomingMessage(msg);
+ return;
+ }
+ if (msg.type != ACTION_SESSION_INITIATE) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ "unknown session", NULL);
+ return;
+ }
+
+ std::string content_type;
+ if (!ParseContentType(msg.protocol, msg.action_elem,
+ &content_type, &error)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ error.text, NULL);
+ return;
+ }
+
+ if (!GetClient(content_type)) {
+ SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify",
+ "unknown content type: " + content_type, NULL);
+ return;
+ }
+
+ session = CreateSession(msg.to, msg.initiator, msg.sid,
+ content_type, true);
+ session->OnIncomingMessage(msg);
+}
+
+void SessionManager::OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza) {
+ // We don't do anything with the response now. If we need to we can forward
+ // it to the session.
+ return;
+}
+
+void SessionManager::OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza) {
+ SessionMessage msg;
+ ParseError error;
+ if (!ParseSessionMessage(orig_stanza, &msg, &error)) {
+ return; // TODO: log somewhere?
+ }
+
+ Session* session = FindSession(msg.sid, msg.to);
+ if (session) {
+ talk_base::scoped_ptr<buzz::XmlElement> synthetic_error;
+ if (!error_stanza) {
+ // A failed send is semantically equivalent to an error response, so we
+ // can just turn the former into the latter.
+ synthetic_error.reset(
+ CreateErrorMessage(orig_stanza, buzz::QN_STANZA_ITEM_NOT_FOUND,
+ "cancel", "Recipient did not respond", NULL));
+ error_stanza = synthetic_error.get();
+ }
+
+ session->OnFailedSend(orig_stanza, error_stanza);
+ }
+}
+
+void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ talk_base::scoped_ptr<buzz::XmlElement> msg(
+ CreateErrorMessage(stanza, name, type, text, extra_info));
+ SignalOutgoingMessage(this, msg.get());
+}
+
+buzz::XmlElement* SessionManager::CreateErrorMessage(
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ buzz::XmlElement* iq = new buzz::XmlElement(buzz::QN_IQ);
+ iq->SetAttr(buzz::QN_TO, stanza->Attr(buzz::QN_FROM));
+ iq->SetAttr(buzz::QN_ID, stanza->Attr(buzz::QN_ID));
+ iq->SetAttr(buzz::QN_TYPE, "error");
+
+ CopyXmlChildren(stanza, iq);
+
+ buzz::XmlElement* error = new buzz::XmlElement(buzz::QN_ERROR);
+ error->SetAttr(buzz::QN_TYPE, type);
+ iq->AddElement(error);
+
+ // If the error name is not in the standard namespace, we have to first add
+ // some error from that namespace.
+ if (name.Namespace() != buzz::NS_STANZA) {
+ error->AddElement(
+ new buzz::XmlElement(buzz::QN_STANZA_UNDEFINED_CONDITION));
+ }
+ error->AddElement(new buzz::XmlElement(name));
+
+ if (extra_info)
+ error->AddElement(new buzz::XmlElement(*extra_info));
+
+ if (text.size() > 0) {
+ // It's okay to always use English here. This text is for debugging
+ // purposes only.
+ buzz::XmlElement* text_elem = new buzz::XmlElement(buzz::QN_STANZA_TEXT);
+ text_elem->SetAttr(buzz::QN_XML_LANG, "en");
+ text_elem->SetBodyText(text);
+ error->AddElement(text_elem);
+ }
+
+ // TODO: Should we include error codes as well for SIP compatibility?
+
+ return iq;
+}
+
+void SessionManager::OnOutgoingMessage(Session* session,
+ const buzz::XmlElement* stanza) {
+ SignalOutgoingMessage(this, stanza);
+}
+
+void SessionManager::OnErrorMessage(BaseSession* session,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info) {
+ SendErrorMessage(stanza, name, type, text, extra_info);
+}
+
+void SessionManager::OnSignalingReady() {
+ for (SessionMap::iterator it = session_map_.begin();
+ it != session_map_.end();
+ ++it) {
+ it->second->OnSignalingReady();
+ }
+}
+
+void SessionManager::OnRequestSignaling(Session* session) {
+ SignalRequestSignaling();
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/sessionmanager.h b/talk/p2p/base/sessionmanager.h
new file mode 100644
index 0000000..9bc1019
--- /dev/null
+++ b/talk/p2p/base/sessionmanager.h
@@ -0,0 +1,187 @@
+/*
+ * 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_P2P_BASE_SESSIONMANAGER_H_
+#define TALK_P2P_BASE_SESSIONMANAGER_H_
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+class Session;
+class BaseSession;
+class SessionClient;
+
+// SessionManager manages session instances
+
+class SessionManager : public sigslot::has_slots<> {
+ public:
+ SessionManager(PortAllocator *allocator,
+ talk_base::Thread *worker_thread = NULL);
+ virtual ~SessionManager();
+
+ PortAllocator *port_allocator() const { return allocator_; }
+ talk_base::Thread *worker_thread() const { return worker_thread_; }
+ talk_base::Thread *signaling_thread() const { return signaling_thread_; }
+
+ int session_timeout() const { return timeout_; }
+ void set_session_timeout(int timeout) { timeout_ = timeout; }
+
+ // Registers support for the given client. If we receive an initiate
+ // describing a session of the given type, we will automatically create a
+ // Session object and notify this client. The client may then accept or
+ // reject the session.
+ void AddClient(const std::string& content_type, SessionClient* client);
+ void RemoveClient(const std::string& content_type);
+ SessionClient* GetClient(const std::string& content_type);
+
+ // Creates a new session. The given name is the JID of the client on whose
+ // behalf we initiate the session.
+ Session *CreateSession(const std::string& local_name,
+ const std::string& content_type);
+
+ // Destroys the given session.
+ void DestroySession(Session *session);
+
+ // Returns the session with the given ID or NULL if none exists.
+ Session *GetSession(const std::string& sid);
+
+ // Terminates all of the sessions created by this manager.
+ void TerminateAll();
+
+ // These are signaled whenever the set of existing sessions changes.
+ sigslot::signal2<Session *, bool> SignalSessionCreate;
+ sigslot::signal1<Session *> SignalSessionDestroy;
+
+ // Determines whether the given stanza is intended for some session.
+ bool IsSessionMessage(const buzz::XmlElement* stanza);
+
+ // Given a sid, initiator, and remote_name, this finds the matching Session
+ Session* FindSession(const std::string& sid,
+ const std::string& remote_name);
+
+ // Called when we receive a stanza for which IsSessionMessage is true.
+ void OnIncomingMessage(const buzz::XmlElement* stanza);
+
+ // Called when we get a response to a message that we sent.
+ void OnIncomingResponse(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* response_stanza);
+
+ // Called if an attempted to send times out or an error is returned. In the
+ // timeout case error_stanza will be NULL
+ void OnFailedSend(const buzz::XmlElement* orig_stanza,
+ const buzz::XmlElement* error_stanza);
+
+ // Signalled each time a session generates a signaling message to send.
+ // Also signalled on errors, but with a NULL session.
+ sigslot::signal2<SessionManager*,
+ const buzz::XmlElement*> SignalOutgoingMessage;
+
+ // Signaled before sessions try to send certain signaling messages. The
+ // client should call OnSignalingReady once it is safe to send them. These
+ // steps are taken so that we don't send signaling messages trying to
+ // re-establish the connectivity of a session when the client cannot send
+ // the messages (and would probably just drop them on the floor).
+ //
+ // Note: you can connect this directly to OnSignalingReady(), if a signalling
+ // check is not supported.
+ sigslot::signal0<> SignalRequestSignaling;
+ void OnSignalingReady();
+
+ private:
+ typedef std::map<std::string, Session*> SessionMap;
+ typedef std::map<std::string, SessionClient*> ClientMap;
+
+ PortAllocator *allocator_;
+ talk_base::Thread *signaling_thread_;
+ talk_base::Thread *worker_thread_;
+ int timeout_;
+ SessionMap session_map_;
+ ClientMap client_map_;
+
+ // Helper function for CreateSession. This is also invoked when we receive
+ // a message attempting to initiate a session with this client.
+ Session *CreateSession(const std::string& local_name,
+ const std::string& initiator,
+ const std::string& sid,
+ const std::string& content_type,
+ bool received_initiate);
+
+ // Attempts to find a registered session type whose description appears as
+ // a child of the session element. Such a child should be present indicating
+ // the application they hope to initiate.
+ std::string FindClient(const buzz::XmlElement* session);
+
+ // Sends a message back to the other client indicating that we found an error
+ // in the stanza they sent. name identifies the error, type is one of the
+ // standard XMPP types (cancel, continue, modify, auth, wait), and text is a
+ // description for debugging purposes.
+ void SendErrorMessage(const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ // Creates and returns an error message from the given components. The
+ // caller is responsible for deleting this.
+ buzz::XmlElement* CreateErrorMessage(
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+
+ // Called each time a session requests signaling.
+ void OnRequestSignaling(Session* session);
+
+ // Called each time a session has an outgoing message.
+ void OnOutgoingMessage(Session* session, const buzz::XmlElement* stanza);
+
+ // Called each time a session has an error to send.
+ void OnErrorMessage(BaseSession* session,
+ const buzz::XmlElement* stanza,
+ const buzz::QName& name,
+ const std::string& type,
+ const std::string& text,
+ const buzz::XmlElement* extra_info);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSIONMANAGER_H_
diff --git a/talk/p2p/base/sessionmessages.cc b/talk/p2p/base/sessionmessages.cc
new file mode 100644
index 0000000..d61b666
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.cc
@@ -0,0 +1,970 @@
+/*
+ * libjingle
+ * Copyright 2010, 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 <stdio.h>
+#include <string>
+#include "talk/p2p/base/sessionmessages.h"
+
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmpp/constants.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/p2ptransport.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace cricket {
+
+ActionType ToActionType(const std::string& type) {
+ if (type == GINGLE_ACTION_INITIATE)
+ return ACTION_SESSION_INITIATE;
+ if (type == GINGLE_ACTION_INFO)
+ return ACTION_SESSION_INFO;
+ if (type == GINGLE_ACTION_ACCEPT)
+ return ACTION_SESSION_ACCEPT;
+ if (type == GINGLE_ACTION_REJECT)
+ return ACTION_SESSION_REJECT;
+ if (type == GINGLE_ACTION_TERMINATE)
+ return ACTION_SESSION_TERMINATE;
+ if (type == GINGLE_ACTION_CANDIDATES)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_SESSION_INITIATE)
+ return ACTION_SESSION_INITIATE;
+ if (type == JINGLE_ACTION_TRANSPORT_INFO)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+ return ACTION_TRANSPORT_ACCEPT;
+ if (type == JINGLE_ACTION_SESSION_INFO)
+ return ACTION_SESSION_INFO;
+ if (type == JINGLE_ACTION_SESSION_ACCEPT)
+ return ACTION_SESSION_ACCEPT;
+ if (type == JINGLE_ACTION_SESSION_TERMINATE)
+ return ACTION_SESSION_TERMINATE;
+ if (type == JINGLE_ACTION_TRANSPORT_INFO)
+ return ACTION_TRANSPORT_INFO;
+ if (type == JINGLE_ACTION_TRANSPORT_ACCEPT)
+ return ACTION_TRANSPORT_ACCEPT;
+ if (type == GINGLE_ACTION_NOTIFY)
+ return ACTION_NOTIFY;
+ if (type == GINGLE_ACTION_UPDATE)
+ return ACTION_UPDATE;
+
+ return ACTION_UNKNOWN;
+}
+
+std::string ToJingleString(ActionType type) {
+ switch (type) {
+ case ACTION_SESSION_INITIATE:
+ return JINGLE_ACTION_SESSION_INITIATE;
+ case ACTION_SESSION_INFO:
+ return JINGLE_ACTION_SESSION_INFO;
+ case ACTION_SESSION_ACCEPT:
+ return JINGLE_ACTION_SESSION_ACCEPT;
+ // Notice that reject and terminate both go to
+ // "session-terminate", but there is no "session-reject".
+ case ACTION_SESSION_REJECT:
+ case ACTION_SESSION_TERMINATE:
+ return JINGLE_ACTION_SESSION_TERMINATE;
+ case ACTION_TRANSPORT_INFO:
+ return JINGLE_ACTION_TRANSPORT_INFO;
+ case ACTION_TRANSPORT_ACCEPT:
+ return JINGLE_ACTION_TRANSPORT_ACCEPT;
+ default:
+ return "";
+ }
+}
+
+std::string ToGingleString(ActionType type) {
+ switch (type) {
+ case ACTION_SESSION_INITIATE:
+ return GINGLE_ACTION_INITIATE;
+ case ACTION_SESSION_INFO:
+ return GINGLE_ACTION_INFO;
+ case ACTION_SESSION_ACCEPT:
+ return GINGLE_ACTION_ACCEPT;
+ case ACTION_SESSION_REJECT:
+ return GINGLE_ACTION_REJECT;
+ case ACTION_SESSION_TERMINATE:
+ return GINGLE_ACTION_TERMINATE;
+ case ACTION_VIEW:
+ return GINGLE_ACTION_VIEW;
+ case ACTION_TRANSPORT_INFO:
+ return GINGLE_ACTION_CANDIDATES;
+ default:
+ return "";
+ }
+}
+
+
+bool IsJingleMessage(const buzz::XmlElement* stanza) {
+ const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+ if (jingle == NULL)
+ return false;
+
+ return (jingle->HasAttr(buzz::QN_ACTION) &&
+ (jingle->HasAttr(QN_SID)
+ // TODO: This works around a bug in old jingle
+ // clients that set QN_ID instead of QN_SID. Once we know
+ // there are no clients which have this bug, we can remove
+ // this code.
+ || jingle->HasAttr(QN_ID)));
+}
+
+bool IsGingleMessage(const buzz::XmlElement* stanza) {
+ const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+ if (session == NULL)
+ return false;
+
+ return (session->HasAttr(buzz::QN_TYPE) &&
+ session->HasAttr(buzz::QN_ID) &&
+ session->HasAttr(QN_INITIATOR));
+}
+
+bool IsSessionMessage(const buzz::XmlElement* stanza) {
+ return (stanza->Name() == buzz::QN_IQ &&
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET &&
+ (IsJingleMessage(stanza) ||
+ IsGingleMessage(stanza)));
+}
+
+bool ParseGingleSessionMessage(const buzz::XmlElement* session,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->protocol = PROTOCOL_GINGLE;
+ std::string type_string = session->Attr(buzz::QN_TYPE);
+ msg->type = ToActionType(type_string);
+ msg->sid = session->Attr(buzz::QN_ID);
+ msg->initiator = session->Attr(QN_INITIATOR);
+ msg->action_elem = session;
+
+ if (msg->type == ACTION_UNKNOWN)
+ return BadParse("unknown action: " + type_string, error);
+
+ return true;
+}
+
+bool ParseJingleSessionMessage(const buzz::XmlElement* jingle,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->protocol = PROTOCOL_JINGLE;
+ std::string type_string = jingle->Attr(buzz::QN_ACTION);
+ msg->type = ToActionType(type_string);
+ msg->sid = jingle->Attr(QN_SID);
+ // TODO: This works around a bug in old jingle clients
+ // that set QN_ID instead of QN_SID. Once we know there are no
+ // clients which have this bug, we can remove this code.
+ if (msg->sid.empty()) {
+ msg->sid = jingle->Attr(buzz::QN_ID);
+ }
+ msg->initiator = GetXmlAttr(jingle, QN_INITIATOR, buzz::STR_EMPTY);
+ msg->action_elem = jingle;
+
+ if (msg->type == ACTION_UNKNOWN)
+ return BadParse("unknown action: " + type_string, error);
+
+ return true;
+}
+
+bool ParseHybridSessionMessage(const buzz::XmlElement* jingle,
+ SessionMessage* msg,
+ ParseError* error) {
+ if (!ParseJingleSessionMessage(jingle, msg, error))
+ return false;
+ msg->protocol = PROTOCOL_HYBRID;
+
+ return true;
+}
+
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+ SessionMessage* msg,
+ ParseError* error) {
+ msg->id = stanza->Attr(buzz::QN_ID);
+ msg->from = stanza->Attr(buzz::QN_FROM);
+ msg->to = stanza->Attr(buzz::QN_TO);
+ msg->stanza = stanza;
+
+ const buzz::XmlElement* jingle = stanza->FirstNamed(QN_JINGLE);
+ const buzz::XmlElement* session = stanza->FirstNamed(QN_GINGLE_SESSION);
+ if (jingle && session)
+ return ParseHybridSessionMessage(jingle, msg, error);
+ if (jingle != NULL)
+ return ParseJingleSessionMessage(jingle, msg, error);
+ if (session != NULL)
+ return ParseGingleSessionMessage(session, msg, error);
+ return false;
+}
+
+buzz::XmlElement* WriteGingleAction(const SessionMessage& msg,
+ const XmlElements& action_elems) {
+ buzz::XmlElement* session = new buzz::XmlElement(QN_GINGLE_SESSION, true);
+ session->AddAttr(buzz::QN_TYPE, ToGingleString(msg.type));
+ session->AddAttr(buzz::QN_ID, msg.sid);
+ session->AddAttr(QN_INITIATOR, msg.initiator);
+ AddXmlChildren(session, action_elems);
+ return session;
+}
+
+buzz::XmlElement* WriteJingleAction(const SessionMessage& msg,
+ const XmlElements& action_elems) {
+ buzz::XmlElement* jingle = new buzz::XmlElement(QN_JINGLE, true);
+ jingle->AddAttr(buzz::QN_ACTION, ToJingleString(msg.type));
+ jingle->AddAttr(QN_SID, msg.sid);
+ // TODO: This works around a bug in old jingle clinets
+ // that expected QN_ID instead of QN_SID. Once we know there are no
+ // clients which have this bug, we can remove this code.
+ jingle->AddAttr(QN_ID, msg.sid);
+ // TODO: Right now, the XMPP server rejects a jingle-only
+ // (non hybrid) message with "feature-not-implemented" if there is
+ // no initiator. Fix the server, and then only set the initiator on
+ // session-initiate messages here.
+ jingle->AddAttr(QN_INITIATOR, msg.initiator);
+ AddXmlChildren(jingle, action_elems);
+ return jingle;
+}
+
+void WriteSessionMessage(const SessionMessage& msg,
+ const XmlElements& action_elems,
+ buzz::XmlElement* stanza) {
+ stanza->SetAttr(buzz::QN_TO, msg.to);
+ stanza->SetAttr(buzz::QN_TYPE, buzz::STR_SET);
+
+ if (msg.protocol == PROTOCOL_GINGLE) {
+ stanza->AddElement(WriteGingleAction(msg, action_elems));
+ } else {
+ stanza->AddElement(WriteJingleAction(msg, action_elems));
+ }
+}
+
+
+TransportParser* GetTransportParser(const TransportParserMap& trans_parsers,
+ const std::string& name) {
+ TransportParserMap::const_iterator map = trans_parsers.find(name);
+ if (map == trans_parsers.end()) {
+ return NULL;
+ } else {
+ return map->second;
+ }
+}
+
+bool ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* candidates_elem,
+ const TransportParserMap& trans_parsers,
+ const std::string& transport_type,
+ Candidates* candidates,
+ ParseError* error) {
+ TransportParser* trans_parser =
+ GetTransportParser(trans_parsers, transport_type);
+ if (trans_parser == NULL)
+ return BadParse("unknown transport type: " + transport_type, error);
+
+ return trans_parser->ParseCandidates(protocol, candidates_elem,
+ candidates, error);
+}
+
+bool ParseGingleTransportInfos(const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ TransportInfo tinfo(CN_OTHER, NS_GINGLE_P2P, Candidates());
+ if (!ParseCandidates(PROTOCOL_GINGLE, action_elem,
+ trans_parsers, NS_GINGLE_P2P,
+ &tinfo.candidates, error))
+ return false;
+
+ bool has_audio = FindContentInfoByName(contents, CN_AUDIO) != NULL;
+ bool has_video = FindContentInfoByName(contents, CN_VIDEO) != NULL;
+
+ // If we don't have media, no need to separate the candidates.
+ if (!has_audio && !has_audio) {
+ tinfos->push_back(tinfo);
+ return true;
+ }
+
+ // If we have media, separate the candidates. Create the
+ // TransportInfo here to avoid copying the candidates.
+ TransportInfo audio_tinfo(CN_AUDIO, NS_GINGLE_P2P, Candidates());
+ TransportInfo video_tinfo(CN_VIDEO, NS_GINGLE_P2P, Candidates());
+ for (Candidates::iterator cand = tinfo.candidates.begin();
+ cand != tinfo.candidates.end(); cand++) {
+ if (cand->name() == GINGLE_CANDIDATE_NAME_RTP ||
+ cand->name() == GINGLE_CANDIDATE_NAME_RTCP) {
+ audio_tinfo.candidates.push_back(*cand);
+ } else if (cand->name() == GINGLE_CANDIDATE_NAME_VIDEO_RTP ||
+ cand->name() == GINGLE_CANDIDATE_NAME_VIDEO_RTCP) {
+ video_tinfo.candidates.push_back(*cand);
+ }
+ }
+
+ if (has_audio) {
+ tinfos->push_back(audio_tinfo);
+ }
+
+ if (has_video) {
+ tinfos->push_back(video_tinfo);
+ }
+
+ return true;
+}
+
+bool ParseJingleTransportInfo(const buzz::XmlElement* trans_elem,
+ const ContentInfo& content,
+ const TransportParserMap& trans_parsers,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ std::string transport_type = trans_elem->Name().Namespace();
+ TransportInfo tinfo(content.name, transport_type, Candidates());
+ if (!ParseCandidates(PROTOCOL_JINGLE, trans_elem,
+ trans_parsers, transport_type,
+ &tinfo.candidates, error))
+ return false;
+
+ tinfos->push_back(tinfo);
+ return true;
+}
+
+bool ParseJingleTransportInfos(const buzz::XmlElement* jingle,
+ const ContentInfos& contents,
+ const TransportParserMap trans_parsers,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ for (const buzz::XmlElement* pair_elem
+ = jingle->FirstNamed(QN_JINGLE_CONTENT);
+ pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_name;
+ if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+ &content_name, error))
+ return false;
+
+ const ContentInfo* content = FindContentInfoByName(contents, content_name);
+ if (!content)
+ return BadParse("Unknown content name: " + content_name, error);
+
+ const buzz::XmlElement* trans_elem;
+ if (!RequireXmlChild(pair_elem, LN_TRANSPORT, &trans_elem, error))
+ return false;
+
+ if (!ParseJingleTransportInfo(trans_elem, *content, trans_parsers,
+ tinfos, error))
+ return false;
+ }
+
+ return true;
+}
+
+buzz::XmlElement* NewTransportElement(const std::string& name) {
+ return new buzz::XmlElement(buzz::QName(true, name, LN_TRANSPORT), true);
+}
+
+bool WriteCandidates(SignalingProtocol protocol,
+ const std::string& trans_type,
+ const Candidates& candidates,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ TransportParser* trans_parser = GetTransportParser(trans_parsers, trans_type);
+ if (trans_parser == NULL)
+ return BadWrite("unknown transport type: " + trans_type, error);
+
+ return trans_parser->WriteCandidates(protocol, candidates, elems, error);
+}
+
+bool WriteGingleTransportInfos(const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (!WriteCandidates(PROTOCOL_GINGLE,
+ tinfo->transport_type, tinfo->candidates,
+ trans_parsers, elems, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool WriteJingleTransportInfo(const TransportInfo& tinfo,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ XmlElements candidate_elems;
+ if (!WriteCandidates(PROTOCOL_JINGLE,
+ tinfo.transport_type, tinfo.candidates, trans_parsers,
+ &candidate_elems, error))
+ return false;
+
+ buzz::XmlElement* trans_elem = NewTransportElement(tinfo.transport_type);
+ AddXmlChildren(trans_elem, candidate_elems);
+ elems->push_back(trans_elem);
+ return true;
+}
+
+void WriteJingleContentPair(const std::string name,
+ const XmlElements& pair_elems,
+ XmlElements* elems) {
+ buzz::XmlElement* pair_elem = new buzz::XmlElement(QN_JINGLE_CONTENT);
+ pair_elem->SetAttr(QN_JINGLE_CONTENT_NAME, name);
+ pair_elem->SetAttr(QN_CREATOR, LN_INITIATOR);
+ AddXmlChildren(pair_elem, pair_elems);
+
+ elems->push_back(pair_elem);
+}
+
+bool WriteJingleTransportInfos(const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ XmlElements pair_elems;
+ if (!WriteJingleTransportInfo(*tinfo, trans_parsers,
+ &pair_elems, error))
+ return false;
+
+ WriteJingleContentPair(tinfo->content_name, pair_elems, elems);
+ }
+
+ return true;
+}
+
+ContentParser* GetContentParser(const ContentParserMap& content_parsers,
+ const std::string& type) {
+ ContentParserMap::const_iterator map = content_parsers.find(type);
+ if (map == content_parsers.end()) {
+ return NULL;
+ } else {
+ return map->second;
+ }
+}
+
+bool ParseContentInfo(SignalingProtocol protocol,
+ const std::string& name,
+ const std::string& type,
+ const buzz::XmlElement* elem,
+ const ContentParserMap& parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ ContentParser* parser = GetContentParser(parsers, type);
+ if (parser == NULL)
+ return BadParse("unknown application content: " + type, error);
+
+ const ContentDescription* desc;
+ if (!parser->ParseContent(protocol, elem, &desc, error))
+ return false;
+
+ contents->push_back(ContentInfo(name, type, desc));
+ return true;
+}
+
+bool ParseContentType(const buzz::XmlElement* parent_elem,
+ std::string* content_type,
+ const buzz::XmlElement** content_elem,
+ ParseError* error) {
+ if (!RequireXmlChild(parent_elem, LN_DESCRIPTION, content_elem, error))
+ return false;
+
+ *content_type = (*content_elem)->Name().Namespace();
+ return true;
+}
+
+bool ParseGingleContentInfos(const buzz::XmlElement* session,
+ const ContentParserMap& content_parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ std::string content_type;
+ const buzz::XmlElement* content_elem;
+ if (!ParseContentType(session, &content_type, &content_elem, error))
+ return false;
+
+ if (content_type == NS_GINGLE_VIDEO) {
+ // A parser parsing audio or video content should look at the
+ // namespace and only parse the codecs relevant to that namespace.
+ // We use this to control which codecs get parsed: first audio,
+ // then video.
+ talk_base::scoped_ptr<buzz::XmlElement> audio_elem(
+ new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT));
+ CopyXmlChildren(content_elem, audio_elem.get());
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+ audio_elem.get(), content_parsers,
+ contents, error))
+ return false;
+
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_VIDEO, NS_JINGLE_RTP,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ } else if (content_type == NS_GINGLE_AUDIO) {
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_AUDIO, NS_JINGLE_RTP,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ } else {
+ if (!ParseContentInfo(PROTOCOL_GINGLE, CN_OTHER, content_type,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ }
+ return true;
+}
+
+bool ParseJingleContentInfos(const buzz::XmlElement* jingle,
+ const ContentParserMap& content_parsers,
+ ContentInfos* contents,
+ ParseError* error) {
+ for (const buzz::XmlElement* pair_elem
+ = jingle->FirstNamed(QN_JINGLE_CONTENT);
+ pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_name;
+ if (!RequireXmlAttr(pair_elem, QN_JINGLE_CONTENT_NAME,
+ &content_name, error))
+ return false;
+
+ std::string content_type;
+ const buzz::XmlElement* content_elem;
+ if (!ParseContentType(pair_elem, &content_type, &content_elem, error))
+ return false;
+
+ if (!ParseContentInfo(PROTOCOL_JINGLE, content_name, content_type,
+ content_elem, content_parsers,
+ contents, error))
+ return false;
+ }
+ return true;
+}
+
+buzz::XmlElement* WriteContentInfo(SignalingProtocol protocol,
+ const ContentInfo& content,
+ const ContentParserMap& parsers,
+ WriteError* error) {
+ ContentParser* parser = GetContentParser(parsers, content.type);
+ if (parser == NULL) {
+ BadWrite("unknown content type: " + content.type, error);
+ return NULL;
+ }
+
+ buzz::XmlElement* elem = NULL;
+ if (!parser->WriteContent(protocol, content.description, &elem, error))
+ return NULL;
+
+ return elem;
+}
+
+bool WriteGingleContentInfos(const ContentInfos& contents,
+ const ContentParserMap& parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ if (contents.size() == 1) {
+ buzz::XmlElement* elem = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.front(), parsers, error);
+ if (!elem)
+ return false;
+
+ elems->push_back(elem);
+ } else if (contents.size() == 2 &&
+ contents.at(0).type == NS_JINGLE_RTP &&
+ contents.at(1).type == NS_JINGLE_RTP) {
+ // Special-case audio + video contents so that they are "merged"
+ // into one "video" content.
+ buzz::XmlElement* audio = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.at(0), parsers, error);
+ if (!audio)
+ return false;
+
+ buzz::XmlElement* video = WriteContentInfo(
+ PROTOCOL_GINGLE, contents.at(1), parsers, error);
+ if (!video) {
+ delete audio;
+ return false;
+ }
+
+ CopyXmlChildren(audio, video);
+ elems->push_back(video);
+ delete audio;
+ } else {
+ return BadWrite("Gingle protocol may only have one content.", error);
+ }
+
+ return true;
+}
+
+const TransportInfo* GetTransportInfoByContentName(
+ const TransportInfos& tinfos, const std::string& content_name) {
+ for (TransportInfos::const_iterator tinfo = tinfos.begin();
+ tinfo != tinfos.end(); ++tinfo) {
+ if (content_name == tinfo->content_name) {
+ return &*tinfo;
+ }
+ }
+ return NULL;
+}
+
+bool WriteJingleContentPairs(const ContentInfos& contents,
+ const ContentParserMap& content_parsers,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); ++content) {
+ const TransportInfo* tinfo =
+ GetTransportInfoByContentName(tinfos, content->name);
+ if (!tinfo)
+ return BadWrite("No transport for content: " + content->name, error);
+
+ XmlElements pair_elems;
+ buzz::XmlElement* elem = WriteContentInfo(
+ PROTOCOL_JINGLE, *content, content_parsers, error);
+ if (!elem)
+ return false;
+ pair_elems.push_back(elem);
+
+ if (!WriteJingleTransportInfo(*tinfo, trans_parsers,
+ &pair_elems, error))
+ return false;
+
+ WriteJingleContentPair(content->name, pair_elems, elems);
+ }
+ return true;
+}
+
+bool ParseContentType(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ std::string* content_type,
+ ParseError* error) {
+ const buzz::XmlElement* content_elem;
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!ParseContentType(action_elem, content_type, &content_elem, error))
+ return false;
+
+ // Internally, we only use NS_JINGLE_RTP.
+ if (*content_type == NS_GINGLE_AUDIO ||
+ *content_type == NS_GINGLE_VIDEO)
+ *content_type = NS_JINGLE_RTP;
+ } else {
+ const buzz::XmlElement* pair_elem
+ = action_elem->FirstNamed(QN_JINGLE_CONTENT);
+ if (pair_elem == NULL)
+ return BadParse("No contents found", error);
+
+ if (!ParseContentType(pair_elem, content_type, &content_elem, error))
+ return false;
+
+ // If there is more than one content type, return an error.
+ for (; pair_elem != NULL;
+ pair_elem = pair_elem->NextNamed(QN_JINGLE_CONTENT)) {
+ std::string content_type2;
+ if (!ParseContentType(pair_elem, &content_type2, &content_elem, error))
+ return false;
+
+ if (content_type2 != *content_type)
+ return BadParse("More than one content type found", error);
+ }
+ }
+
+ return true;
+}
+
+bool ParseSessionInitiate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& trans_parsers,
+ SessionInitiate* init,
+ ParseError* error) {
+ init->owns_contents = true;
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!ParseGingleContentInfos(action_elem, content_parsers,
+ &init->contents, error))
+ return false;
+
+ if (!ParseGingleTransportInfos(action_elem, init->contents, trans_parsers,
+ &init->transports, error))
+ return false;
+ } else {
+ if (!ParseJingleContentInfos(action_elem, content_parsers,
+ &init->contents, error))
+ return false;
+
+ if (!ParseJingleTransportInfos(action_elem, init->contents, trans_parsers,
+ &init->transports, error))
+ return false;
+ }
+
+ return true;
+}
+
+
+bool WriteSessionInitiate(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ if (!WriteGingleContentInfos(contents, content_parsers, elems, error))
+ return false;
+
+ if (!WriteGingleTransportInfos(tinfos, transport_parsers,
+ elems, error))
+ return false;
+ } else {
+ if (!WriteJingleContentPairs(contents, content_parsers,
+ tinfos, transport_parsers,
+ elems, error))
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseSessionAccept(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ SessionAccept* accept,
+ ParseError* error) {
+ return ParseSessionInitiate(protocol, action_elem,
+ content_parsers, transport_parsers,
+ accept, error);
+}
+
+bool WriteSessionAccept(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ return WriteSessionInitiate(protocol, contents, tinfos,
+ content_parsers, transport_parsers,
+ elems, error);
+}
+
+bool ParseSessionTerminate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ SessionTerminate* term,
+ ParseError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ const buzz::XmlElement* reason_elem = action_elem->FirstElement();
+ if (reason_elem != NULL) {
+ term->reason = reason_elem->Name().LocalPart();
+ const buzz::XmlElement *debug_elem = reason_elem->FirstElement();
+ if (debug_elem != NULL) {
+ term->debug_reason = debug_elem->Name().LocalPart();
+ }
+ }
+ return true;
+ } else {
+ const buzz::XmlElement* reason_elem =
+ action_elem->FirstNamed(QN_JINGLE_REASON);
+ if (reason_elem) {
+ reason_elem = reason_elem->FirstElement();
+ if (reason_elem) {
+ term->reason = reason_elem->Name().LocalPart();
+ }
+ }
+ return true;
+ }
+}
+
+void WriteSessionTerminate(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems) {
+ if (protocol == PROTOCOL_GINGLE) {
+ elems->push_back(new buzz::XmlElement(
+ buzz::QName(true, NS_GINGLE, term.reason)));
+ } else {
+ if (!term.reason.empty()) {
+ buzz::XmlElement* reason_elem = new buzz::XmlElement(QN_JINGLE_REASON);
+ reason_elem->AddElement(new buzz::XmlElement(
+ buzz::QName(true, NS_JINGLE, term.reason)));
+ elems->push_back(reason_elem);
+ }
+ }
+}
+
+bool ParseTransportInfos(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ TransportInfos* tinfos,
+ ParseError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return ParseGingleTransportInfos(
+ action_elem, contents, trans_parsers, tinfos, error);
+ } else {
+ return ParseJingleTransportInfos(
+ action_elem, contents, trans_parsers, tinfos, error);
+ }
+}
+
+bool WriteTransportInfos(SignalingProtocol protocol,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ return WriteGingleTransportInfos(tinfos, trans_parsers,
+ elems, error);
+ } else {
+ return WriteJingleTransportInfos(tinfos, trans_parsers,
+ elems, error);
+ }
+}
+
+bool ParseSessionNotify(const buzz::XmlElement* action_elem,
+ SessionNotify* notify, ParseError* error) {
+ const buzz::XmlElement* notify_elem;
+ for (notify_elem = action_elem->FirstNamed(QN_GINGLE_NOTIFY);
+ notify_elem != NULL;
+ notify_elem = notify_elem->NextNamed(QN_GINGLE_NOTIFY)) {
+ // Note that a subsequent notify element for the same user will override a
+ // previous. We don't merge them.
+ std::string nick(notify_elem->Attr(QN_GINGLE_NOTIFY_NICK));
+ if (nick != buzz::STR_EMPTY) {
+ MediaSources sources;
+ const buzz::XmlElement* source_elem;
+ for (source_elem = notify_elem->FirstNamed(QN_GINGLE_NOTIFY_SOURCE);
+ source_elem != NULL;
+ source_elem = source_elem->NextNamed(QN_GINGLE_NOTIFY_SOURCE)) {
+ std::string ssrc = source_elem->Attr(QN_GINGLE_NOTIFY_SOURCE_SSRC);
+ if (ssrc != buzz::STR_EMPTY) {
+ std::string mtype = source_elem->Attr(QN_GINGLE_NOTIFY_SOURCE_MTYPE);
+ if (mtype == GINGLE_NOTIFY_SOURCE_MTYPE_AUDIO) {
+ sources.audio_ssrc = strtoul(ssrc.c_str(), NULL, 10);
+ } else if (mtype == GINGLE_NOTIFY_SOURCE_MTYPE_VIDEO) {
+ sources.video_ssrc = strtoul(ssrc.c_str(), NULL, 10);
+ }
+ }
+ }
+
+ notify->nickname_to_sources.insert(
+ std::pair<std::string, MediaSources>(nick, sources));
+ }
+ }
+
+ return true;
+}
+
+bool GetUriTarget(const std::string& prefix, const std::string& str,
+ std::string* after) {
+ size_t pos = str.find(prefix);
+ if (pos == std::string::npos)
+ return false;
+
+ *after = str.substr(pos + prefix.size(), std::string::npos);
+ return true;
+}
+
+bool ParseSessionUpdate(const buzz::XmlElement* action_elem,
+ SessionUpdate* update, ParseError* error) {
+ // TODO: Parse the update message.
+ return true;
+}
+
+void WriteSessionView(const SessionView& view, XmlElements* elems) {
+ std::vector<VideoViewRequest>::const_iterator it;
+ for (it = view.view_requests.begin(); it != view.view_requests.end(); it++) {
+ talk_base::scoped_ptr<buzz::XmlElement> view_elem(
+ new buzz::XmlElement(QN_GINGLE_VIEW));
+ if (view_elem.get() == NULL) {
+ return;
+ }
+
+ view_elem->SetAttr(QN_GINGLE_VIEW_TYPE, GINGLE_VIEW_TYPE_STATIC);
+ view_elem->SetAttr(QN_GINGLE_VIEW_NICK, it->nick_name);
+ view_elem->SetAttr(QN_GINGLE_VIEW_MEDIA_TYPE,
+ GINGLE_VIEW_MEDIA_TYPE_VIDEO);
+
+ // A 32-bit uint, expressed as decimal, has a max of 10 digits, plus one
+ // for the null.
+ char str[11];
+ int result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->ssrc);
+ if (result < 0 || result >= ARRAY_SIZE(str)) {
+ continue;
+ }
+ view_elem->SetAttr(QN_GINGLE_VIEW_SSRC, str);
+
+ // Include video-specific parameters in a child <params> element.
+ talk_base::scoped_ptr<buzz::XmlElement> params_elem(
+ new buzz::XmlElement(QN_GINGLE_VIEW_PARAMS));
+ if (params_elem.get() == NULL) {
+ return;
+ }
+
+ result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->width);
+ if (result < 0 || result >= ARRAY_SIZE(str)) {
+ continue;
+ }
+ params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_WIDTH, str);
+
+ result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->height);
+ if (result < 0 || result >= ARRAY_SIZE(str)) {
+ continue;
+ }
+ params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_HEIGHT, str);
+
+ result = talk_base::sprintfn(str, ARRAY_SIZE(str), "%u", it->framerate);
+ if (result < 0 || result >= ARRAY_SIZE(str)) {
+ continue;
+ }
+ params_elem->SetAttr(QN_GINGLE_VIEW_PARAMS_FRAMERATE, str);
+
+ view_elem->AddElement(params_elem.release());
+ elems->push_back(view_elem.release());
+ }
+}
+
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+ SessionRedirect* redirect) {
+ const buzz::XmlElement* error_elem = GetXmlChild(stanza, LN_ERROR);
+ if (error_elem == NULL)
+ return false;
+
+ const buzz::XmlElement* redirect_elem =
+ error_elem->FirstNamed(QN_GINGLE_REDIRECT);
+ if (redirect_elem == NULL)
+ redirect_elem = error_elem->FirstNamed(buzz::QN_STANZA_REDIRECT);
+ if (redirect_elem == NULL)
+ return false;
+
+ if (!GetUriTarget(STR_REDIRECT_PREFIX, redirect_elem->BodyText(),
+ &redirect->target))
+ return false;
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/sessionmessages.h b/talk/p2p/base/sessionmessages.h
new file mode 100644
index 0000000..affb4d9
--- /dev/null
+++ b/talk/p2p/base/sessionmessages.h
@@ -0,0 +1,272 @@
+/*
+ * libjingle
+ * Copyright 2010, 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_P2P_BASE_SESSIONMESSAGES_H_
+#define TALK_P2P_BASE_SESSIONMESSAGES_H_
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "talk/xmllite/xmlelement.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessiondescription.h" // Needed to delete contents.
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class Candidate;
+class ContentParser;
+class TransportParser;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+typedef std::map<std::string, ContentParser*> ContentParserMap;
+typedef std::map<std::string, TransportParser*> TransportParserMap;
+
+enum ActionType {
+ ACTION_UNKNOWN,
+
+ ACTION_SESSION_INITIATE,
+ ACTION_SESSION_INFO,
+ ACTION_SESSION_ACCEPT,
+ ACTION_SESSION_REJECT,
+ ACTION_SESSION_TERMINATE,
+
+ ACTION_TRANSPORT_INFO,
+ ACTION_TRANSPORT_ACCEPT,
+
+ // TODO: Make better names for these when we think of a
+ // "jingley" way of signaling them. Even better, remove them from
+ // being needed at all.
+ ACTION_NOTIFY,
+ ACTION_UPDATE,
+ ACTION_VIEW,
+};
+
+// Abstraction of a <jingle> element within an <iq> stanza, per XMPP
+// standard XEP-166. Can be serialized into multiple protocols,
+// including the standard (Jingle) and the draft standard (Gingle).
+// In general, used to communicate actions related to a p2p session,
+// such accept, initiate, terminate, etc.
+
+struct SessionMessage {
+ SessionMessage() : action_elem(NULL), stanza(NULL) {}
+
+ SessionMessage(SignalingProtocol protocol, ActionType type,
+ const std::string& sid, const std::string& initiator) :
+ protocol(protocol), type(type), sid(sid), initiator(initiator),
+ action_elem(NULL), stanza(NULL) {}
+
+ std::string id;
+ std::string from;
+ std::string to;
+ SignalingProtocol protocol;
+ ActionType type;
+ std::string sid; // session id
+ std::string initiator;
+
+ // Used for further parsing when necessary.
+ // Represents <session> or <jingle>.
+ const buzz::XmlElement* action_elem;
+ // Mostly used for debugging.
+ const buzz::XmlElement* stanza;
+};
+
+// A TransportInfo is NOT a transport-info message. It is comparable
+// to a "ContentInfo". A transport-info message is basically just a
+// collection of TransportInfos.
+struct TransportInfo {
+ TransportInfo() {}
+
+ TransportInfo(const std::string& content_name,
+ const std::string& transport_type,
+ const Candidates& candidates)
+ : content_name(content_name),
+ transport_type(transport_type),
+ candidates(candidates) {}
+
+ std::string content_name;
+ std::string transport_type; // xmlns of <transport>
+ Candidates candidates;
+};
+
+typedef std::vector<TransportInfo> TransportInfos;
+
+struct SessionInitiate {
+ SessionInitiate() : owns_contents(false) {}
+
+ ~SessionInitiate() {
+ if (owns_contents) {
+ for (ContentInfos::iterator content = contents.begin();
+ content != contents.end(); content++) {
+ delete content->description;
+ }
+ }
+ }
+
+ // Caller takes ownership of contents.
+ ContentInfos ClearContents() {
+ ContentInfos out;
+ contents.swap(out);
+ owns_contents = false;
+ return out;
+ }
+
+ bool owns_contents;
+ ContentInfos contents;
+ TransportInfos transports;
+};
+
+// Right now, a SessionAccept is functionally equivalent to a SessionInitiate.
+typedef SessionInitiate SessionAccept;
+
+struct SessionTerminate {
+ SessionTerminate() {}
+
+ explicit SessionTerminate(const std::string& reason) :
+ reason(reason) {}
+
+ std::string reason;
+ std::string debug_reason;
+};
+
+struct SessionRedirect {
+ std::string target;
+};
+
+// Holds the ssrcs for a user's media streams.
+struct MediaSources {
+ uint32 audio_ssrc;
+ uint32 video_ssrc;
+ MediaSources() : audio_ssrc(0), video_ssrc(0) {}
+};
+
+typedef std::map<std::string, MediaSources> StringToMediaSourcesMap;
+
+struct SessionNotify {
+ // A mapping of room users (identified by their nicknames) to their ssrcs.
+ StringToMediaSourcesMap nickname_to_sources;
+};
+
+// TODO: Populate the update message.
+struct SessionUpdate {
+};
+
+// Represents an individual <view> element in the <session type="view">
+// message.
+struct VideoViewRequest {
+ std::string nick_name;
+ uint32 ssrc;
+ uint32 width;
+ uint32 height;
+ uint32 framerate;
+
+ VideoViewRequest(const std::string& nick_name, uint32 ssrc, uint32 width,
+ uint32 height, uint32 framerate) :
+ nick_name(nick_name), ssrc(ssrc), width(width), height(height),
+ framerate(framerate) {}
+};
+
+typedef std::vector<VideoViewRequest> VideoViewRequestVector;
+
+struct SessionView {
+ VideoViewRequestVector view_requests;
+};
+
+bool IsSessionMessage(const buzz::XmlElement* stanza);
+bool ParseSessionMessage(const buzz::XmlElement* stanza,
+ SessionMessage* msg,
+ ParseError* error);
+// Will return an error if there is more than one content type.
+bool ParseContentType(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ std::string* content_type,
+ ParseError* error);
+void WriteSessionMessage(const SessionMessage& msg,
+ const XmlElements& action_elems,
+ buzz::XmlElement* stanza);
+bool ParseSessionInitiate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ SessionInitiate* init,
+ ParseError* error);
+bool WriteSessionInitiate(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ XmlElements* elems,
+ WriteError* error);
+bool ParseSessionAccept(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ SessionAccept* accept,
+ ParseError* error);
+bool WriteSessionAccept(SignalingProtocol protocol,
+ const ContentInfos& contents,
+ const TransportInfos& tinfos,
+ const ContentParserMap& content_parsers,
+ const TransportParserMap& transport_parsers,
+ XmlElements* elems,
+ WriteError* error);
+bool ParseSessionTerminate(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ SessionTerminate* term,
+ ParseError* error);
+void WriteSessionTerminate(SignalingProtocol protocol,
+ const SessionTerminate& term,
+ XmlElements* elems);
+// Since a TransportInfo is not a transport-info message, and a
+// transport-info message is just a collection of TransportInfos, we
+// say Parse/Write TransportInfos for transport-info messages.
+bool ParseTransportInfos(SignalingProtocol protocol,
+ const buzz::XmlElement* action_elem,
+ const ContentInfos& contents,
+ const TransportParserMap& trans_parsers,
+ TransportInfos* tinfos,
+ ParseError* error);
+bool WriteTransportInfos(SignalingProtocol protocol,
+ const TransportInfos& tinfos,
+ const TransportParserMap& trans_parsers,
+ XmlElements* elems,
+ WriteError* error);
+bool ParseSessionNotify(const buzz::XmlElement* action_elem,
+ SessionNotify* notify, ParseError* error);
+bool ParseSessionUpdate(const buzz::XmlElement* action_elem,
+ SessionUpdate* update, ParseError* error);
+void WriteSessionView(const SessionView& view, XmlElements* elems);
+// Handles both Gingle and Jingle syntax.
+bool FindSessionRedirect(const buzz::XmlElement* stanza,
+ SessionRedirect* redirect);
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_SESSIONMESSAGES_H_
diff --git a/talk/p2p/base/stun.cc b/talk/p2p/base/stun.cc
new file mode 100644
index 0000000..ad8b5ce
--- /dev/null
+++ b/talk/p2p/base/stun.cc
@@ -0,0 +1,580 @@
+/*
+ * 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 "talk/p2p/base/stun.h"
+
+#include <cstring>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+
+using talk_base::ByteBuffer;
+
+namespace cricket {
+
+const std::string STUN_ERROR_REASON_BAD_REQUEST = "BAD REQUEST";
+const std::string STUN_ERROR_REASON_UNAUTHORIZED = "UNAUTHORIZED";
+const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE = "UNKNOWN ATTRIBUTE";
+const std::string STUN_ERROR_REASON_STALE_CREDENTIALS = "STALE CREDENTIALS";
+const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE = "INTEGRITY CHECK FAILURE";
+const std::string STUN_ERROR_REASON_MISSING_USERNAME = "MISSING USERNAME";
+const std::string STUN_ERROR_REASON_USE_TLS = "USE TLS";
+const std::string STUN_ERROR_REASON_SERVER_ERROR = "SERVER ERROR";
+const std::string STUN_ERROR_REASON_GLOBAL_FAILURE = "GLOBAL FAILURE";
+
+StunMessage::StunMessage() : type_(0), length_(0),
+ transaction_id_("0000000000000000") {
+ ASSERT(transaction_id_.size() == 16);
+ attrs_ = new std::vector<StunAttribute*>();
+}
+
+StunMessage::~StunMessage() {
+ for (unsigned i = 0; i < attrs_->size(); i++)
+ delete (*attrs_)[i];
+ delete attrs_;
+}
+
+void StunMessage::SetTransactionID(const std::string& str) {
+ ASSERT(str.size() == 16);
+ transaction_id_ = str;
+}
+
+void StunMessage::AddAttribute(StunAttribute* attr) {
+ attrs_->push_back(attr);
+ length_ += attr->length() + 4;
+}
+
+const StunAddressAttribute*
+StunMessage::GetAddress(StunAttributeType type) const {
+ switch (type) {
+ case STUN_ATTR_MAPPED_ADDRESS:
+ case STUN_ATTR_RESPONSE_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS:
+ case STUN_ATTR_CHANGED_ADDRESS:
+ case STUN_ATTR_REFLECTED_FROM:
+ case STUN_ATTR_ALTERNATE_SERVER:
+ case STUN_ATTR_DESTINATION_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS2:
+ return reinterpret_cast<const StunAddressAttribute*>(GetAttribute(type));
+
+ default:
+ ASSERT(0);
+ return 0;
+ }
+}
+
+const StunUInt32Attribute*
+StunMessage::GetUInt32(StunAttributeType type) const {
+ switch (type) {
+ case STUN_ATTR_CHANGE_REQUEST:
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_BANDWIDTH:
+ case STUN_ATTR_OPTIONS:
+ return reinterpret_cast<const StunUInt32Attribute*>(GetAttribute(type));
+
+ default:
+ ASSERT(0);
+ return 0;
+ }
+}
+
+const StunByteStringAttribute*
+StunMessage::GetByteString(StunAttributeType type) const {
+ switch (type) {
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_PASSWORD:
+ case STUN_ATTR_MESSAGE_INTEGRITY:
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_MAGIC_COOKIE:
+ return reinterpret_cast<const StunByteStringAttribute*>(GetAttribute(type));
+
+ default:
+ ASSERT(0);
+ return 0;
+ }
+}
+
+const StunErrorCodeAttribute* StunMessage::GetErrorCode() const {
+ return reinterpret_cast<const StunErrorCodeAttribute*>(
+ GetAttribute(STUN_ATTR_ERROR_CODE));
+}
+
+const StunUInt16ListAttribute* StunMessage::GetUnknownAttributes() const {
+ return reinterpret_cast<const StunUInt16ListAttribute*>(
+ GetAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES));
+}
+
+const StunTransportPrefsAttribute* StunMessage::GetTransportPrefs() const {
+ return reinterpret_cast<const StunTransportPrefsAttribute*>(
+ GetAttribute(STUN_ATTR_TRANSPORT_PREFERENCES));
+}
+
+const StunAttribute* StunMessage::GetAttribute(StunAttributeType type) const {
+ for (unsigned i = 0; i < attrs_->size(); i++) {
+ if ((*attrs_)[i]->type() == type)
+ return (*attrs_)[i];
+ }
+ return 0;
+}
+
+bool StunMessage::Read(ByteBuffer* buf) {
+ if (!buf->ReadUInt16(&type_))
+ return false;
+
+ if (type_ & 0x8000) {
+ // rtp and rtcp set MSB of first byte, since first two bits are version,
+ // and version is always 2 (10). If set, this is not a stun packet.
+ return false;
+ }
+
+ if (!buf->ReadUInt16(&length_))
+ return false;
+
+ std::string transaction_id;
+ if (!buf->ReadString(&transaction_id, 16))
+ return false;
+ ASSERT(transaction_id.size() == 16);
+ transaction_id_ = transaction_id;
+
+ if (length_ > buf->Length())
+ return false;
+
+ attrs_->resize(0);
+
+ size_t rest = buf->Length() - length_;
+ while (buf->Length() > rest) {
+ uint16 attr_type, attr_length;
+ if (!buf->ReadUInt16(&attr_type))
+ return false;
+ if (!buf->ReadUInt16(&attr_length))
+ return false;
+
+ StunAttribute* attr = StunAttribute::Create(attr_type, attr_length);
+ if (!attr || !attr->Read(buf))
+ return false;
+
+ attrs_->push_back(attr);
+ }
+
+ if (buf->Length() != rest) {
+ // fixme: shouldn't be doing this
+ LOG(LERROR) << "wrong message length (" << rest << " != " << buf->Length()
+ << ")";
+ return false;
+ }
+
+ return true;
+}
+
+void StunMessage::Write(ByteBuffer* buf) const {
+ buf->WriteUInt16(type_);
+ buf->WriteUInt16(length_);
+ buf->WriteString(transaction_id_);
+
+ for (unsigned i = 0; i < attrs_->size(); i++) {
+ buf->WriteUInt16((*attrs_)[i]->type());
+ buf->WriteUInt16((*attrs_)[i]->length());
+ (*attrs_)[i]->Write(buf);
+ }
+}
+
+StunAttribute::StunAttribute(uint16 type, uint16 length)
+ : type_(type), length_(length) {
+}
+
+StunAttribute* StunAttribute::Create(uint16 type, uint16 length) {
+ switch (type) {
+ case STUN_ATTR_MAPPED_ADDRESS:
+ case STUN_ATTR_RESPONSE_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS:
+ case STUN_ATTR_CHANGED_ADDRESS:
+ case STUN_ATTR_REFLECTED_FROM:
+ case STUN_ATTR_ALTERNATE_SERVER:
+ case STUN_ATTR_DESTINATION_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS2:
+ if (length != StunAddressAttribute::SIZE)
+ return 0;
+ return new StunAddressAttribute(type);
+
+ case STUN_ATTR_CHANGE_REQUEST:
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_BANDWIDTH:
+ case STUN_ATTR_OPTIONS:
+ if (length != StunUInt32Attribute::SIZE)
+ return 0;
+ return new StunUInt32Attribute(type);
+
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_PASSWORD:
+ case STUN_ATTR_MAGIC_COOKIE:
+ return (length % 4 == 0) ? new StunByteStringAttribute(type, length) : 0;
+
+ case STUN_ATTR_MESSAGE_INTEGRITY:
+ return (length == 20) ? new StunByteStringAttribute(type, length) : 0;
+
+ case STUN_ATTR_DATA:
+ return new StunByteStringAttribute(type, length);
+
+ case STUN_ATTR_ERROR_CODE:
+ if (length < StunErrorCodeAttribute::MIN_SIZE)
+ return 0;
+ return new StunErrorCodeAttribute(type, length);
+
+ case STUN_ATTR_UNKNOWN_ATTRIBUTES:
+ return (length % 2 == 0) ? new StunUInt16ListAttribute(type, length) : 0;
+
+ case STUN_ATTR_TRANSPORT_PREFERENCES:
+ if ((length != StunTransportPrefsAttribute::SIZE1) &&
+ (length != StunTransportPrefsAttribute::SIZE2))
+ return 0;
+ return new StunTransportPrefsAttribute(type, length);
+
+ default:
+ return 0;
+ }
+}
+
+StunAddressAttribute* StunAttribute::CreateAddress(uint16 type) {
+ switch (type) {
+ case STUN_ATTR_MAPPED_ADDRESS:
+ case STUN_ATTR_RESPONSE_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS:
+ case STUN_ATTR_CHANGED_ADDRESS:
+ case STUN_ATTR_REFLECTED_FROM:
+ case STUN_ATTR_ALTERNATE_SERVER:
+ case STUN_ATTR_DESTINATION_ADDRESS:
+ case STUN_ATTR_SOURCE_ADDRESS2:
+ return new StunAddressAttribute(type);
+
+ default:
+ ASSERT(false);
+ return 0;
+ }
+}
+
+StunUInt32Attribute* StunAttribute::CreateUInt32(uint16 type) {
+ switch (type) {
+ case STUN_ATTR_CHANGE_REQUEST:
+ case STUN_ATTR_LIFETIME:
+ case STUN_ATTR_BANDWIDTH:
+ case STUN_ATTR_OPTIONS:
+ return new StunUInt32Attribute(type);
+
+ default:
+ ASSERT(false);
+ return 0;
+ }
+}
+
+StunByteStringAttribute* StunAttribute::CreateByteString(uint16 type) {
+ switch (type) {
+ case STUN_ATTR_USERNAME:
+ case STUN_ATTR_PASSWORD:
+ case STUN_ATTR_MESSAGE_INTEGRITY:
+ case STUN_ATTR_DATA:
+ case STUN_ATTR_MAGIC_COOKIE:
+ return new StunByteStringAttribute(type, 0);
+
+ default:
+ ASSERT(false);
+ return 0;
+ }
+}
+
+StunErrorCodeAttribute* StunAttribute::CreateErrorCode() {
+ return new StunErrorCodeAttribute(
+ STUN_ATTR_ERROR_CODE, StunErrorCodeAttribute::MIN_SIZE);
+}
+
+StunUInt16ListAttribute* StunAttribute::CreateUnknownAttributes() {
+ return new StunUInt16ListAttribute(STUN_ATTR_UNKNOWN_ATTRIBUTES, 0);
+}
+
+StunTransportPrefsAttribute* StunAttribute::CreateTransportPrefs() {
+ return new StunTransportPrefsAttribute(
+ STUN_ATTR_TRANSPORT_PREFERENCES, StunTransportPrefsAttribute::SIZE1);
+}
+
+StunAddressAttribute::StunAddressAttribute(uint16 type)
+ : StunAttribute(type, SIZE), family_(0), port_(0), ip_(0) {
+}
+
+bool StunAddressAttribute::Read(ByteBuffer* buf) {
+ uint8 dummy;
+ if (!buf->ReadUInt8(&dummy))
+ return false;
+ if (!buf->ReadUInt8(&family_))
+ return false;
+ if (!buf->ReadUInt16(&port_))
+ return false;
+ if (!buf->ReadUInt32(&ip_))
+ return false;
+ return true;
+}
+
+void StunAddressAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt8(0);
+ buf->WriteUInt8(family_);
+ buf->WriteUInt16(port_);
+ buf->WriteUInt32(ip_);
+}
+
+StunUInt32Attribute::StunUInt32Attribute(uint16 type)
+ : StunAttribute(type, SIZE), bits_(0) {
+}
+
+bool StunUInt32Attribute::GetBit(int index) const {
+ ASSERT((0 <= index) && (index < 32));
+ return static_cast<bool>((bits_ >> index) & 0x1);
+}
+
+void StunUInt32Attribute::SetBit(int index, bool value) {
+ ASSERT((0 <= index) && (index < 32));
+ bits_ &= ~(1 << index);
+ bits_ |= value ? (1 << index) : 0;
+}
+
+bool StunUInt32Attribute::Read(ByteBuffer* buf) {
+ if (!buf->ReadUInt32(&bits_))
+ return false;
+ return true;
+}
+
+void StunUInt32Attribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt32(bits_);
+}
+
+StunByteStringAttribute::StunByteStringAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length), bytes_(0) {
+}
+
+StunByteStringAttribute::~StunByteStringAttribute() {
+ delete [] bytes_;
+}
+
+void StunByteStringAttribute::SetBytes(char* bytes, uint16 length) {
+ delete [] bytes_;
+ bytes_ = bytes;
+ SetLength(length);
+}
+
+void StunByteStringAttribute::CopyBytes(const char* bytes) {
+ CopyBytes(bytes, static_cast<uint16>(strlen(bytes)));
+}
+
+void StunByteStringAttribute::CopyBytes(const void* bytes, uint16 length) {
+ char* new_bytes = new char[length];
+ std::memcpy(new_bytes, bytes, length);
+ SetBytes(new_bytes, length);
+}
+
+uint8 StunByteStringAttribute::GetByte(int index) const {
+ ASSERT(bytes_ != NULL);
+ ASSERT((0 <= index) && (index < length()));
+ return static_cast<uint8>(bytes_[index]);
+}
+
+void StunByteStringAttribute::SetByte(int index, uint8 value) {
+ ASSERT(bytes_ != NULL);
+ ASSERT((0 <= index) && (index < length()));
+ bytes_[index] = value;
+}
+
+bool StunByteStringAttribute::Read(ByteBuffer* buf) {
+ bytes_ = new char[length()];
+ if (!buf->ReadBytes(bytes_, length()))
+ return false;
+ return true;
+}
+
+void StunByteStringAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteBytes(bytes_, length());
+}
+
+StunErrorCodeAttribute::StunErrorCodeAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length), class_(0), number_(0) {
+}
+
+StunErrorCodeAttribute::~StunErrorCodeAttribute() {
+}
+
+void StunErrorCodeAttribute::SetErrorCode(uint32 code) {
+ class_ = (uint8)((code >> 8) & 0x7);
+ number_ = (uint8)(code & 0xff);
+}
+
+void StunErrorCodeAttribute::SetReason(const std::string& reason) {
+ SetLength(MIN_SIZE + static_cast<uint16>(reason.size()));
+ reason_ = reason;
+}
+
+bool StunErrorCodeAttribute::Read(ByteBuffer* buf) {
+ uint32 val;
+ if (!buf->ReadUInt32(&val))
+ return false;
+
+ if ((val >> 11) != 0)
+ LOG(LERROR) << "error-code bits not zero";
+
+ SetErrorCode(val);
+
+ if (!buf->ReadString(&reason_, length() - 4))
+ return false;
+
+ return true;
+}
+
+void StunErrorCodeAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt32(error_code());
+ buf->WriteString(reason_);
+}
+
+StunUInt16ListAttribute::StunUInt16ListAttribute(uint16 type, uint16 length)
+ : StunAttribute(type, length) {
+ attr_types_ = new std::vector<uint16>();
+}
+
+StunUInt16ListAttribute::~StunUInt16ListAttribute() {
+ delete attr_types_;
+}
+
+size_t StunUInt16ListAttribute::Size() const {
+ return attr_types_->size();
+}
+
+uint16 StunUInt16ListAttribute::GetType(int index) const {
+ return (*attr_types_)[index];
+}
+
+void StunUInt16ListAttribute::SetType(int index, uint16 value) {
+ (*attr_types_)[index] = value;
+}
+
+void StunUInt16ListAttribute::AddType(uint16 value) {
+ attr_types_->push_back(value);
+ SetLength(static_cast<uint16>(attr_types_->size() * 2));
+}
+
+bool StunUInt16ListAttribute::Read(ByteBuffer* buf) {
+ for (int i = 0; i < length() / 2; i++) {
+ uint16 attr;
+ if (!buf->ReadUInt16(&attr))
+ return false;
+ attr_types_->push_back(attr);
+ }
+ return true;
+}
+
+void StunUInt16ListAttribute::Write(ByteBuffer* buf) const {
+ for (unsigned i = 0; i < attr_types_->size(); i++)
+ buf->WriteUInt16((*attr_types_)[i]);
+}
+
+StunTransportPrefsAttribute::StunTransportPrefsAttribute(
+ uint16 type, uint16 length)
+ : StunAttribute(type, length), preallocate_(false), prefs_(0), addr_(0) {
+}
+
+StunTransportPrefsAttribute::~StunTransportPrefsAttribute() {
+ delete addr_;
+}
+
+void StunTransportPrefsAttribute::SetPreallocateAddress(
+ StunAddressAttribute* addr) {
+ if (!addr) {
+ preallocate_ = false;
+ addr_ = 0;
+ SetLength(SIZE1);
+ } else {
+ preallocate_ = true;
+ addr_ = addr;
+ SetLength(SIZE2);
+ }
+}
+
+bool StunTransportPrefsAttribute::Read(ByteBuffer* buf) {
+ uint32 val;
+ if (!buf->ReadUInt32(&val))
+ return false;
+
+ if ((val >> 3) != 0)
+ LOG(LERROR) << "transport-preferences bits not zero";
+
+ preallocate_ = static_cast<bool>((val >> 2) & 0x1);
+ prefs_ = (uint8)(val & 0x3);
+
+ if (preallocate_ && (prefs_ == 3))
+ LOG(LERROR) << "transport-preferences imcompatible P and Typ";
+
+ if (!preallocate_) {
+ if (length() != StunUInt32Attribute::SIZE)
+ return false;
+ } else {
+ if (length() != StunUInt32Attribute::SIZE + StunAddressAttribute::SIZE)
+ return false;
+
+ addr_ = new StunAddressAttribute(STUN_ATTR_SOURCE_ADDRESS);
+ addr_->Read(buf);
+ }
+
+ return true;
+}
+
+void StunTransportPrefsAttribute::Write(ByteBuffer* buf) const {
+ buf->WriteUInt32((preallocate_ ? 4 : 0) | prefs_);
+
+ if (preallocate_)
+ addr_->Write(buf);
+}
+
+StunMessageType GetStunResponseType(StunMessageType request_type) {
+ switch (request_type) {
+ case STUN_SHARED_SECRET_REQUEST:
+ return STUN_SHARED_SECRET_RESPONSE;
+ case STUN_ALLOCATE_REQUEST:
+ return STUN_ALLOCATE_RESPONSE;
+ case STUN_SEND_REQUEST:
+ return STUN_SEND_RESPONSE;
+ default:
+ return STUN_BINDING_RESPONSE;
+ }
+}
+
+StunMessageType GetStunErrorResponseType(StunMessageType request_type) {
+ switch (request_type) {
+ case STUN_SHARED_SECRET_REQUEST:
+ return STUN_SHARED_SECRET_ERROR_RESPONSE;
+ case STUN_ALLOCATE_REQUEST:
+ return STUN_ALLOCATE_ERROR_RESPONSE;
+ case STUN_SEND_REQUEST:
+ return STUN_SEND_ERROR_RESPONSE;
+ default:
+ return STUN_BINDING_ERROR_RESPONSE;
+ }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stun.h b/talk/p2p/base/stun.h
new file mode 100644
index 0000000..2282fed
--- /dev/null
+++ b/talk/p2p/base/stun.h
@@ -0,0 +1,365 @@
+/*
+ * 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 __STUN_H__
+#define __STUN_H__
+
+// This file contains classes for dealing with the STUN and TURN protocols.
+// Both protocols use the same wire format.
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/bytebuffer.h"
+
+namespace cricket {
+
+// These are the types of STUN & TURN messages as of last check.
+enum StunMessageType {
+ STUN_BINDING_REQUEST = 0x0001,
+ STUN_BINDING_RESPONSE = 0x0101,
+ STUN_BINDING_ERROR_RESPONSE = 0x0111,
+ STUN_SHARED_SECRET_REQUEST = 0x0002,
+ STUN_SHARED_SECRET_RESPONSE = 0x0102,
+ STUN_SHARED_SECRET_ERROR_RESPONSE = 0x0112,
+ STUN_ALLOCATE_REQUEST = 0x0003,
+ STUN_ALLOCATE_RESPONSE = 0x0103,
+ STUN_ALLOCATE_ERROR_RESPONSE = 0x0113,
+ STUN_SEND_REQUEST = 0x0004,
+ STUN_SEND_RESPONSE = 0x0104,
+ STUN_SEND_ERROR_RESPONSE = 0x0114,
+ STUN_DATA_INDICATION = 0x0115
+};
+
+// These are the types of attributes defined in STUN & TURN. Next to each is
+// the name of the class (T is StunTAttribute) that implements that type.
+enum StunAttributeType {
+ STUN_ATTR_MAPPED_ADDRESS = 0x0001, // Address
+ STUN_ATTR_RESPONSE_ADDRESS = 0x0002, // Address
+ STUN_ATTR_CHANGE_REQUEST = 0x0003, // UInt32
+ STUN_ATTR_SOURCE_ADDRESS = 0x0004, // Address
+ STUN_ATTR_CHANGED_ADDRESS = 0x0005, // Address
+ STUN_ATTR_USERNAME = 0x0006, // ByteString, multiple of 4 bytes
+ STUN_ATTR_PASSWORD = 0x0007, // ByteString, multiple of 4 bytes
+ STUN_ATTR_MESSAGE_INTEGRITY = 0x0008, // ByteString, 20 bytes
+ STUN_ATTR_ERROR_CODE = 0x0009, // ErrorCode
+ STUN_ATTR_UNKNOWN_ATTRIBUTES = 0x000a, // UInt16List
+ STUN_ATTR_REFLECTED_FROM = 0x000b, // Address
+ STUN_ATTR_TRANSPORT_PREFERENCES = 0x000c, // TransportPrefs
+ STUN_ATTR_LIFETIME = 0x000d, // UInt32
+ STUN_ATTR_ALTERNATE_SERVER = 0x000e, // Address
+ STUN_ATTR_MAGIC_COOKIE = 0x000f, // ByteString, 4 bytes
+ STUN_ATTR_BANDWIDTH = 0x0010, // UInt32
+ STUN_ATTR_DESTINATION_ADDRESS = 0x0011, // Address
+ STUN_ATTR_SOURCE_ADDRESS2 = 0x0012, // Address
+ STUN_ATTR_DATA = 0x0013, // ByteString
+ STUN_ATTR_OPTIONS = 0x8001 // UInt32
+};
+
+enum StunErrorCodes {
+ STUN_ERROR_BAD_REQUEST = 400,
+ STUN_ERROR_UNAUTHORIZED = 401,
+ STUN_ERROR_UNKNOWN_ATTRIBUTE = 420,
+ STUN_ERROR_STALE_CREDENTIALS = 430,
+ STUN_ERROR_INTEGRITY_CHECK_FAILURE = 431,
+ STUN_ERROR_MISSING_USERNAME = 432,
+ STUN_ERROR_USE_TLS = 433,
+ STUN_ERROR_SERVER_ERROR = 500,
+ STUN_ERROR_GLOBAL_FAILURE = 600
+};
+
+extern const std::string STUN_ERROR_REASON_BAD_REQUEST;
+extern const std::string STUN_ERROR_REASON_UNAUTHORIZED;
+extern const std::string STUN_ERROR_REASON_UNKNOWN_ATTRIBUTE;
+extern const std::string STUN_ERROR_REASON_STALE_CREDENTIALS;
+extern const std::string STUN_ERROR_REASON_INTEGRITY_CHECK_FAILURE;
+extern const std::string STUN_ERROR_REASON_MISSING_USERNAME;
+extern const std::string STUN_ERROR_REASON_USE_TLS;
+extern const std::string STUN_ERROR_REASON_SERVER_ERROR;
+extern const std::string STUN_ERROR_REASON_GLOBAL_FAILURE;
+
+class StunAttribute;
+class StunAddressAttribute;
+class StunUInt32Attribute;
+class StunByteStringAttribute;
+class StunErrorCodeAttribute;
+class StunUInt16ListAttribute;
+class StunTransportPrefsAttribute;
+
+// Records a complete STUN/TURN message. Each message consists of a type and
+// any number of attributes. Each attribute is parsed into an instance of an
+// appropriate class (see above). The Get* methods will return instances of
+// that attribute class.
+class StunMessage {
+public:
+ StunMessage();
+ ~StunMessage();
+
+ StunMessageType type() const { return static_cast<StunMessageType>(type_); }
+ uint16 length() const { return length_; }
+ const std::string& transaction_id() const { return transaction_id_; }
+
+ void SetType(StunMessageType type) { type_ = type; }
+ void SetTransactionID(const std::string& str);
+
+ const StunAddressAttribute* GetAddress(StunAttributeType type) const;
+ const StunUInt32Attribute* GetUInt32(StunAttributeType type) const;
+ const StunByteStringAttribute* GetByteString(StunAttributeType type) const;
+ const StunErrorCodeAttribute* GetErrorCode() const;
+ const StunUInt16ListAttribute* GetUnknownAttributes() const;
+ const StunTransportPrefsAttribute* GetTransportPrefs() const;
+
+ void AddAttribute(StunAttribute* attr);
+
+ // Parses the STUN/TURN packet in the given buffer and records it here. The
+ // return value indicates whether this was successful.
+ bool Read(talk_base::ByteBuffer* buf);
+
+ // Writes this object into a STUN/TURN packet. Return value is true if
+ // successful.
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ uint16 type_;
+ uint16 length_;
+ std::string transaction_id_;
+ std::vector<StunAttribute*>* attrs_;
+
+ const StunAttribute* GetAttribute(StunAttributeType type) const;
+};
+
+// Base class for all STUN/TURN attributes.
+class StunAttribute {
+public:
+ virtual ~StunAttribute() {}
+
+ StunAttributeType type() const {
+ return static_cast<StunAttributeType>(type_);
+ }
+ uint16 length() const { return length_; }
+
+ // Reads the body (not the type or length) for this type of attribute from
+ // the given buffer. Return value is true if successful.
+ virtual bool Read(talk_base::ByteBuffer* buf) = 0;
+
+ // Writes the body (not the type or length) to the given buffer. Return
+ // value is true if successful.
+ virtual void Write(talk_base::ByteBuffer* buf) const = 0;
+
+ // Creates an attribute object with the given type and len.
+ static StunAttribute* Create(uint16 type, uint16 length);
+
+ // Creates an attribute object with the given type and smallest length.
+ static StunAddressAttribute* CreateAddress(uint16 type);
+ static StunUInt32Attribute* CreateUInt32(uint16 type);
+ static StunByteStringAttribute* CreateByteString(uint16 type);
+ static StunErrorCodeAttribute* CreateErrorCode();
+ static StunUInt16ListAttribute* CreateUnknownAttributes();
+ static StunTransportPrefsAttribute* CreateTransportPrefs();
+
+protected:
+ StunAttribute(uint16 type, uint16 length);
+
+ void SetLength(uint16 length) { length_ = length; }
+
+private:
+ uint16 type_;
+ uint16 length_;
+};
+
+// Implements STUN/TURN attributes that record an Internet address.
+class StunAddressAttribute : public StunAttribute {
+public:
+ StunAddressAttribute(uint16 type);
+
+#if (_MSC_VER < 1300)
+ enum { SIZE = 8 };
+#else
+ static const uint16 SIZE = 8;
+#endif
+
+ uint8 family() const { return family_; }
+ uint16 port() const { return port_; }
+ uint32 ip() const { return ip_; }
+
+ void SetFamily(uint8 family) { family_ = family; }
+ void SetIP(uint32 ip) { ip_ = ip; }
+ void SetPort(uint16 port) { port_ = port; }
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ uint8 family_;
+ uint16 port_;
+ uint32 ip_;
+};
+
+// Implements STUN/TURN attributs that record a 32-bit integer.
+class StunUInt32Attribute : public StunAttribute {
+public:
+ StunUInt32Attribute(uint16 type);
+
+#if (_MSC_VER < 1300)
+ enum { SIZE = 4 };
+#else
+ static const uint16 SIZE = 4;
+#endif
+
+ uint32 value() const { return bits_; }
+
+ void SetValue(uint32 bits) { bits_ = bits; }
+
+ bool GetBit(int index) const;
+ void SetBit(int index, bool value);
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ uint32 bits_;
+};
+
+// Implements STUN/TURN attributs that record an arbitrary byte string
+class StunByteStringAttribute : public StunAttribute {
+public:
+ StunByteStringAttribute(uint16 type, uint16 length);
+ ~StunByteStringAttribute();
+
+ const char* bytes() const { return bytes_; }
+
+ void SetBytes(char* bytes, uint16 length);
+
+ void CopyBytes(const char* bytes); // uses strlen
+ void CopyBytes(const void* bytes, uint16 length);
+
+ uint8 GetByte(int index) const;
+ void SetByte(int index, uint8 value);
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ char* bytes_;
+};
+
+// Implements STUN/TURN attributs that record an error code.
+class StunErrorCodeAttribute : public StunAttribute {
+public:
+ StunErrorCodeAttribute(uint16 type, uint16 length);
+ ~StunErrorCodeAttribute();
+
+#if (_MSC_VER < 1300)
+ enum { MIN_SIZE = 4 };
+#else
+ static const uint16 MIN_SIZE = 4;
+#endif
+
+ uint32 error_code() const { return (class_ << 8) | number_; }
+ uint8 error_class() const { return class_; }
+ uint8 number() const { return number_; }
+ const std::string& reason() const { return reason_; }
+
+ void SetErrorCode(uint32 code);
+ void SetErrorClass(uint8 eclass) { class_ = eclass; }
+ void SetNumber(uint8 number) { number_ = number; }
+ void SetReason(const std::string& reason);
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ uint8 class_;
+ uint8 number_;
+ std::string reason_;
+};
+
+// Implements STUN/TURN attributs that record a list of attribute names.
+class StunUInt16ListAttribute : public StunAttribute {
+public:
+ StunUInt16ListAttribute(uint16 type, uint16 length);
+ ~StunUInt16ListAttribute();
+
+ size_t Size() const;
+ uint16 GetType(int index) const;
+ void SetType(int index, uint16 value);
+ void AddType(uint16 value);
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ std::vector<uint16>* attr_types_;
+};
+
+// Implements the TURN TRANSPORT-PREFS attribute, which provides information
+// about the ports to allocate.
+class StunTransportPrefsAttribute : public StunAttribute {
+public:
+ StunTransportPrefsAttribute(uint16 type, uint16 length);
+ ~StunTransportPrefsAttribute();
+
+#if (_MSC_VER < 1300)
+ enum { SIZE1 = 4, SIZE2 = 12 };
+#else
+ static const uint16 SIZE1 = 4;
+ static const uint16 SIZE2 = 12;
+#endif
+
+ bool preallocate() const { return preallocate_; }
+ uint8 preference_type() const { return prefs_; }
+ const StunAddressAttribute* address() const { return addr_; }
+
+ void SetPreferenceType(uint8 prefs) { prefs_ = prefs; }
+
+ // Sets the preallocate address to the given value, or if 0 is given, it sets
+ // to not preallocate.
+ void SetPreallocateAddress(StunAddressAttribute* addr);
+
+ bool Read(talk_base::ByteBuffer* buf);
+ void Write(talk_base::ByteBuffer* buf) const;
+
+private:
+ bool preallocate_;
+ uint8 prefs_;
+ StunAddressAttribute* addr_;
+};
+
+// The special MAGIC-COOKIE attribute is used to distinguish TURN packets from
+// other kinds of traffic.
+const char STUN_MAGIC_COOKIE_VALUE[] = { 0x72, char(0xc6), 0x4b, char(0xc6) };
+
+// Returns the (successful) response type for the given request type.
+StunMessageType GetStunResponseType(StunMessageType request_type);
+
+// Returns the error response type for the given request type.
+StunMessageType GetStunErrorResponseType(StunMessageType request_type);
+
+} // namespace cricket
+
+#endif // __STUN_H__
diff --git a/talk/p2p/base/stunport.cc b/talk/p2p/base/stunport.cc
new file mode 100644
index 0000000..aa6e1d3
--- /dev/null
+++ b/talk/p2p/base/stunport.cc
@@ -0,0 +1,255 @@
+/*
+ * 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 "talk/p2p/base/stunport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/helpers.h"
+#include "talk/base/nethelpers.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+// TODO: Move these to a common place (used in relayport too)
+const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts
+const int RETRY_DELAY = 50; // 50ms, from ICE spec
+const int RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs
+
+// Handles a binding request sent to the STUN server.
+class StunPortBindingRequest : public StunRequest {
+ public:
+ StunPortBindingRequest(StunPort* port, bool keep_alive,
+ const talk_base::SocketAddress& addr)
+ : port_(port), keep_alive_(keep_alive), server_addr_(addr) {
+ start_time_ = talk_base::Time();
+ }
+
+ virtual ~StunPortBindingRequest() {
+ }
+
+ const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+
+ virtual void Prepare(StunMessage* request) {
+ request->SetType(STUN_BINDING_REQUEST);
+ }
+
+ virtual void OnResponse(StunMessage* response) {
+ const StunAddressAttribute* addr_attr =
+ response->GetAddress(STUN_ATTR_MAPPED_ADDRESS);
+ if (!addr_attr) {
+ LOG(LS_ERROR) << "Binding response missing mapped address.";
+ } else if (addr_attr->family() != 1) {
+ LOG(LS_ERROR) << "Binding address has bad family";
+ } else {
+ talk_base::SocketAddress addr(addr_attr->ip(), addr_attr->port());
+ port_->AddAddress(addr, "udp", true);
+ }
+
+ // We will do a keep-alive regardless of whether this request suceeds.
+ // This should have almost no impact on network usage.
+ if (keep_alive_) {
+ port_->requests_.SendDelayed(
+ new StunPortBindingRequest(port_, true, server_addr_),
+ KEEPALIVE_DELAY);
+ }
+ }
+
+ virtual void OnErrorResponse(StunMessage* response) {
+ const StunErrorCodeAttribute* attr = response->GetErrorCode();
+ if (!attr) {
+ LOG(LS_ERROR) << "Bad allocate response error code";
+ } else {
+ LOG(LS_ERROR) << "Binding error response:"
+ << " class=" << attr->error_class()
+ << " number=" << attr->number()
+ << " reason='" << attr->reason() << "'";
+ }
+
+ port_->SignalAddressError(port_);
+
+ if (keep_alive_
+ && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+ port_->requests_.SendDelayed(
+ new StunPortBindingRequest(port_, true, server_addr_),
+ KEEPALIVE_DELAY);
+ }
+ }
+
+ virtual void OnTimeout() {
+ LOG(LS_ERROR) << "Binding request timed out from "
+ << port_->socket_->GetLocalAddress(NULL).ToString()
+ << " (" << port_->network()->name() << ")";
+
+ port_->SignalAddressError(port_);
+
+ if (keep_alive_
+ && (talk_base::TimeSince(start_time_) <= RETRY_TIMEOUT)) {
+ port_->requests_.SendDelayed(
+ new StunPortBindingRequest(port_, true, server_addr_),
+ RETRY_DELAY);
+ }
+ }
+
+ private:
+ StunPort* port_;
+ bool keep_alive_;
+ talk_base::SocketAddress server_addr_;
+ uint32 start_time_;
+};
+
+const std::string STUN_PORT_TYPE("stun");
+
+StunPort::StunPort(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ uint32 ip, int min_port, int max_port,
+ const talk_base::SocketAddress& server_addr)
+ : Port(thread, STUN_PORT_TYPE, factory, network, ip, min_port, max_port),
+ server_addr_(server_addr),
+ requests_(thread),
+ socket_(NULL),
+ error_(0),
+ resolver_(NULL) {
+ requests_.SignalSendPacket.connect(this, &StunPort::OnSendPacket);
+}
+
+bool StunPort::Init() {
+ socket_ = factory_->CreateUdpSocket(
+ talk_base::SocketAddress(ip_, 0), min_port_, max_port_);
+ if (!socket_) {
+ LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+ return false;
+ }
+ socket_->SignalReadPacket.connect(this, &StunPort::OnReadPacket);
+ return true;
+}
+
+StunPort::~StunPort() {
+ if (resolver_) {
+ resolver_->Destroy(false);
+ }
+ delete socket_;
+}
+
+void StunPort::PrepareAddress() {
+ // We will keep pinging the stun server to make sure our NAT pin-hole stays
+ // open during the call.
+ if (server_addr_.IsUnresolved()) {
+ ResolveStunAddress();
+ } else {
+ requests_.Send(new StunPortBindingRequest(this, true, server_addr_));
+ }
+}
+
+void StunPort::PrepareSecondaryAddress() {
+ // DNS resolution of the secondary address is not currently supported.
+ ASSERT(!server_addr2_.IsAny());
+ requests_.Send(new StunPortBindingRequest(this, false, server_addr2_));
+}
+
+Connection* StunPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ if (address.protocol() != "udp")
+ return NULL;
+
+ Connection* conn = new ProxyConnection(this, 0, address);
+ AddConnection(conn);
+ return conn;
+}
+
+int StunPort::SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload) {
+ int sent = socket_->SendTo(data, size, addr);
+ if (sent < 0) {
+ error_ = socket_->GetError();
+ LOG_J(LS_ERROR, this) << "UDP send of " << size
+ << " bytes failed with error " << error_;
+ }
+ return sent;
+}
+
+int StunPort::SetOption(talk_base::Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int StunPort::GetError() {
+ return error_;
+}
+
+void StunPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ ASSERT(socket == socket_);
+
+ // Look for a response from the STUN server.
+ // Even if the response doesn't match one of our outstanding requests, we
+ // will eat it because it might be a response to a retransmitted packet, and
+ // we already cleared the request when we got the first response.
+ ASSERT(!server_addr_.IsUnresolved());
+ if (remote_addr == server_addr_ || remote_addr == server_addr2_) {
+ requests_.CheckResponse(data, size);
+ return;
+ }
+
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr);
+ }
+}
+
+void StunPort::ResolveStunAddress() {
+ if (resolver_)
+ return;
+
+ resolver_ = new talk_base::AsyncResolver();
+ resolver_->SignalWorkDone.connect(this, &StunPort::OnResolveResult);
+ resolver_->set_address(server_addr_);
+ resolver_->Start();
+}
+
+void StunPort::OnResolveResult(talk_base::SignalThread* t) {
+ ASSERT(t == resolver_);
+ if (resolver_->error() != 0) {
+ LOG_J(LS_WARNING, this) << "StunPort: stun host lookup received error "
+ << resolver_->error();
+ SignalAddressError(this);
+ }
+
+ server_addr_ = resolver_->address();
+ PrepareAddress();
+}
+
+// TODO: merge this with SendTo above.
+void StunPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
+ StunPortBindingRequest* sreq = static_cast<StunPortBindingRequest*>(req);
+ if (socket_->SendTo(data, size, sreq->server_addr()) < 0)
+ PLOG(LERROR, socket_->GetError()) << "sendto";
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stunport.h b/talk/p2p/base/stunport.h
new file mode 100644
index 0000000..0b41724
--- /dev/null
+++ b/talk/p2p/base/stunport.h
@@ -0,0 +1,117 @@
+/*
+ * 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_P2P_BASE_STUNPORT_H_
+#define TALK_P2P_BASE_STUNPORT_H_
+
+#include <string>
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/udpport.h"
+#include "talk/p2p/base/stunrequest.h"
+
+namespace talk_base {
+class AsyncResolver;
+class SignalThread;
+}
+
+namespace cricket {
+
+extern const std::string STUN_PORT_TYPE;
+
+// Communicates using the address on the outside of a NAT.
+class StunPort : public Port {
+ public:
+ static StunPort* Create(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ uint32 ip, int min_port, int max_port,
+ const talk_base::SocketAddress& server_addr) {
+ StunPort* port = new StunPort(thread, factory, network,
+ ip, min_port, max_port, server_addr);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+ virtual ~StunPort();
+
+ const talk_base::SocketAddress& server_addr() const { return server_addr_; }
+ void set_server_addr(const talk_base::SocketAddress& addr) {
+ server_addr_ = addr;
+ }
+
+ const talk_base::SocketAddress& server_addr2() const { return server_addr2_; }
+ void set_server_addr2(const talk_base::SocketAddress& addr) {
+ server_addr2_ = addr;
+ }
+
+ virtual void PrepareAddress();
+
+ // This will contact the secondary server and signal another candidate
+ // address for this port (which may be the same as the first address).
+ void PrepareSecondaryAddress();
+
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ protected:
+ StunPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip, int min_port, int max_port,
+ const talk_base::SocketAddress& server_addr);
+ bool Init();
+
+ virtual int SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload);
+
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ private:
+ // DNS resolution of the STUN server.
+ void ResolveStunAddress();
+ void OnResolveResult(talk_base::SignalThread* thread);
+ // Sends STUN requests to the server.
+ void OnSendPacket(const void* data, size_t size, StunRequest* req);
+
+ talk_base::SocketAddress server_addr_;
+ talk_base::SocketAddress server_addr2_;
+ StunRequestManager requests_;
+ talk_base::AsyncPacketSocket* socket_;
+ int error_;
+ talk_base::AsyncResolver* resolver_;
+
+ friend class StunPortBindingRequest;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_STUNPORT_H_
diff --git a/talk/p2p/base/stunrequest.cc b/talk/p2p/base/stunrequest.cc
new file mode 100644
index 0000000..1ad121e
--- /dev/null
+++ b/talk/p2p/base/stunrequest.cc
@@ -0,0 +1,200 @@
+/*
+ * 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 "talk/p2p/base/stunrequest.h"
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+const uint32 MSG_STUN_SEND = 1;
+
+const int MAX_SENDS = 9;
+const int DELAY_UNIT = 100; // 100 milliseconds
+const int DELAY_MAX_FACTOR = 16;
+
+StunRequestManager::StunRequestManager(talk_base::Thread* thread)
+ : thread_(thread) {
+}
+
+StunRequestManager::~StunRequestManager() {
+ while (requests_.begin() != requests_.end()) {
+ StunRequest *request = requests_.begin()->second;
+ requests_.erase(requests_.begin());
+ delete request;
+ }
+}
+
+void StunRequestManager::Send(StunRequest* request) {
+ SendDelayed(request, 0);
+}
+
+void StunRequestManager::SendDelayed(StunRequest* request, int delay) {
+ request->set_manager(this);
+ ASSERT(requests_.find(request->id()) == requests_.end());
+ request->Construct();
+ requests_[request->id()] = request;
+ thread_->PostDelayed(delay, request, MSG_STUN_SEND, NULL);
+}
+
+void StunRequestManager::Remove(StunRequest* request) {
+ ASSERT(request->manager() == this);
+ RequestMap::iterator iter = requests_.find(request->id());
+ if (iter != requests_.end()) {
+ ASSERT(iter->second == request);
+ requests_.erase(iter);
+ thread_->Clear(request);
+ }
+}
+
+void StunRequestManager::Clear() {
+ std::vector<StunRequest*> requests;
+ for (RequestMap::iterator i = requests_.begin(); i != requests_.end(); ++i)
+ requests.push_back(i->second);
+
+ for (uint32 i = 0; i < requests.size(); ++i)
+ Remove(requests[i]);
+}
+
+bool StunRequestManager::CheckResponse(StunMessage* msg) {
+ RequestMap::iterator iter = requests_.find(msg->transaction_id());
+ if (iter == requests_.end())
+ return false;
+
+ StunRequest* request = iter->second;
+ if (msg->type() == GetStunResponseType(request->type())) {
+ request->OnResponse(msg);
+ } else if (msg->type() == GetStunErrorResponseType(request->type())) {
+ request->OnErrorResponse(msg);
+ } else {
+ LOG(LERROR) << "Received response with wrong type: " << msg->type()
+ << " (expecting " << GetStunResponseType(request->type()) << ")";
+ return false;
+ }
+
+ delete request;
+ return true;
+}
+
+bool StunRequestManager::CheckResponse(const char* data, size_t size) {
+ // Check the appropriate bytes of the stream to see if they match the
+ // transaction ID of a response we are expecting.
+
+ if (size < 20)
+ return false;
+
+ std::string id;
+ id.append(data + 4, 16);
+
+ RequestMap::iterator iter = requests_.find(id);
+ if (iter == requests_.end())
+ return false;
+
+ // Parse the STUN message and continue processing as usual.
+
+ talk_base::ByteBuffer buf(data, size);
+ StunMessage msg;
+ if (!msg.Read(&buf))
+ return false;
+
+ return CheckResponse(&msg);
+}
+
+StunRequest::StunRequest()
+ : count_(0), timeout_(false), manager_(0),
+ id_(talk_base::CreateRandomString(16)), msg_(new StunMessage()),
+ tstamp_(0) {
+ msg_->SetTransactionID(id_);
+}
+
+StunRequest::StunRequest(StunMessage* request)
+ : count_(0), timeout_(false), manager_(0),
+ id_(request->transaction_id()), msg_(request) {
+}
+
+StunRequest::~StunRequest() {
+ ASSERT(manager_ != NULL);
+ if (manager_) {
+ manager_->Remove(this);
+ manager_->thread_->Clear(this);
+ }
+ delete msg_;
+}
+
+void StunRequest::Construct() {
+ if (msg_->type() == 0) {
+ Prepare(msg_);
+ ASSERT(msg_->transaction_id() == id_);
+ ASSERT(msg_->type() != 0);
+ }
+}
+
+StunMessageType StunRequest::type() {
+ ASSERT(msg_ != NULL);
+ return msg_->type();
+}
+
+void StunRequest::set_manager(StunRequestManager* manager) {
+ ASSERT(!manager_);
+ manager_ = manager;
+}
+
+void StunRequest::OnMessage(talk_base::Message* pmsg) {
+ ASSERT(manager_ != NULL);
+ ASSERT(pmsg->message_id == MSG_STUN_SEND);
+
+ if (timeout_) {
+ OnTimeout();
+ delete this;
+ return;
+ }
+
+ tstamp_ = talk_base::Time();
+
+ talk_base::ByteBuffer buf;
+ msg_->Write(&buf);
+ manager_->SignalSendPacket(buf.Data(), buf.Length(), this);
+
+ int delay = GetNextDelay();
+ manager_->thread_->PostDelayed(delay, this, MSG_STUN_SEND, NULL);
+}
+
+uint32 StunRequest::Elapsed() const {
+ return talk_base::TimeSince(tstamp_);
+}
+
+int StunRequest::GetNextDelay() {
+ int delay = DELAY_UNIT * talk_base::_min(1 << count_, DELAY_MAX_FACTOR);
+ count_ += 1;
+ if (count_ == MAX_SENDS)
+ timeout_ = true;
+ return delay;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stunrequest.h b/talk/p2p/base/stunrequest.h
new file mode 100644
index 0000000..fea2c99
--- /dev/null
+++ b/talk/p2p/base/stunrequest.h
@@ -0,0 +1,129 @@
+/*
+ * 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_P2P_BASE_STUNREQUEST_H_
+#define TALK_P2P_BASE_STUNREQUEST_H_
+
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stun.h"
+#include <map>
+#include <string>
+
+namespace cricket {
+
+class StunRequest;
+
+// Manages a set of STUN requests, sending and resending until we receive a
+// response or determine that the request has timed out.
+class StunRequestManager {
+public:
+ StunRequestManager(talk_base::Thread* thread);
+ ~StunRequestManager();
+
+ // Starts sending the given request (perhaps after a delay).
+ void Send(StunRequest* request);
+ void SendDelayed(StunRequest* request, int delay);
+
+ // Removes a stun request that was added previously. This will happen
+ // automatically when a request succeeds, fails, or times out.
+ void Remove(StunRequest* request);
+
+ // Removes all stun requests that were added previously.
+ void Clear();
+
+ // Determines whether the given message is a response to one of the
+ // outstanding requests, and if so, processes it appropriately.
+ bool CheckResponse(StunMessage* msg);
+ bool CheckResponse(const char* data, size_t size);
+
+ // Raised when there are bytes to be sent.
+ sigslot::signal3<const void*, size_t, StunRequest*> SignalSendPacket;
+
+private:
+ typedef std::map<std::string, StunRequest*> RequestMap;
+
+ talk_base::Thread* thread_;
+ RequestMap requests_;
+
+ friend class StunRequest;
+};
+
+// Represents an individual request to be sent. The STUN message can either be
+// constructed beforehand or built on demand.
+class StunRequest : public talk_base::MessageHandler {
+public:
+ StunRequest();
+ StunRequest(StunMessage* request);
+ virtual ~StunRequest();
+
+ // Causes our wrapped StunMessage to be Prepared
+ void Construct();
+
+ // The manager handling this request (if it has been scheduled for sending).
+ StunRequestManager* manager() { return manager_; }
+
+ // Returns the transaction ID of this request.
+ const std::string& id() { return id_; }
+
+ // Returns the STUN type of the request message.
+ StunMessageType type();
+
+ // Handles messages for sending and timeout.
+ void OnMessage(talk_base::Message* pmsg);
+
+ // Time elapsed since last send (in ms)
+ uint32 Elapsed() const;
+
+protected:
+ int count_;
+ bool timeout_;
+
+ // Fills in a request object to be sent. Note that request's transaction ID
+ // will already be set and cannot be changed.
+ virtual void Prepare(StunMessage* request) {}
+
+ // Called when the message receives a response or times out.
+ virtual void OnResponse(StunMessage* response) {}
+ virtual void OnErrorResponse(StunMessage* response) {}
+ virtual void OnTimeout() {}
+ virtual int GetNextDelay();
+
+private:
+ StunRequestManager* manager_;
+ std::string id_;
+ StunMessage* msg_;
+ uint32 tstamp_;
+
+ void set_manager(StunRequestManager* manager);
+
+ friend class StunRequestManager;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_STUNREQUEST_H_
diff --git a/talk/p2p/base/stunserver.cc b/talk/p2p/base/stunserver.cc
new file mode 100644
index 0000000..a0686a1
--- /dev/null
+++ b/talk/p2p/base/stunserver.cc
@@ -0,0 +1,164 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <errno.h>
+#endif // POSIX
+
+#include "talk/p2p/base/stunserver.h"
+#include "talk/base/bytebuffer.h"
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+StunServer::StunServer(talk_base::AsyncUDPSocket* socket) : socket_(socket) {
+ socket_->SignalReadPacket.connect(this, &StunServer::OnPacket);
+}
+
+StunServer::~StunServer() {
+ socket_->SignalReadPacket.disconnect(this);
+}
+
+void StunServer::OnPacket(
+ talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+
+ // TODO: If appropriate, look for the magic cookie before parsing.
+
+ // Parse the STUN message.
+ talk_base::ByteBuffer bbuf(buf, size);
+ StunMessage msg;
+ if (!msg.Read(&bbuf)) {
+ SendErrorResponse(msg, remote_addr, 400, "Bad Request");
+ return;
+ }
+
+ // TODO: If this is UDP, then we shouldn't allow non-fully-parsed messages.
+
+ // TODO: If unknown non-optiional (<= 0x7fff) attributes are found, send a
+ // 420 "Unknown Attribute" response.
+
+ // TODO: Check that a message-integrity attribute was given (or send 401
+ // "Unauthorized"). Check that a username attribute was given (or send
+ // 432 "Missing Username"). Look up the username and password. If it
+ // is missing or the HMAC is wrong, send 431 "Integrity Check Failure".
+
+ // Send the message to the appropriate handler function.
+ switch (msg.type()) {
+ case STUN_BINDING_REQUEST:
+ OnBindingRequest(&msg, remote_addr);
+ return;
+
+ case STUN_ALLOCATE_REQUEST:
+ OnAllocateRequest(&msg, remote_addr);
+ return;
+
+ default:
+ SendErrorResponse(msg, remote_addr, 600, "Operation Not Supported");
+ }
+}
+
+void StunServer::OnBindingRequest(
+ StunMessage* msg, const talk_base::SocketAddress& remote_addr) {
+ StunMessage response;
+ response.SetType(STUN_BINDING_RESPONSE);
+ response.SetTransactionID(msg->transaction_id());
+
+ // Tell the user the address that we received their request from.
+ StunAddressAttribute* mapped_addr =
+ StunAttribute::CreateAddress(STUN_ATTR_MAPPED_ADDRESS);
+ mapped_addr->SetFamily(1);
+ mapped_addr->SetPort(remote_addr.port());
+ mapped_addr->SetIP(remote_addr.ip());
+ response.AddAttribute(mapped_addr);
+
+ // Tell the user the address that we are sending the response from.
+ // This method should not be called if socket address is not
+ // allocated yet.
+ bool allocated;
+ talk_base::SocketAddress local_addr = socket_->GetLocalAddress(&allocated);
+ ASSERT(allocated);
+
+ StunAddressAttribute* source_addr =
+ StunAttribute::CreateAddress(STUN_ATTR_SOURCE_ADDRESS);
+ source_addr->SetFamily(1);
+ source_addr->SetPort(local_addr.port());
+ source_addr->SetIP(local_addr.ip());
+ response.AddAttribute(source_addr);
+
+ // TODO: Add username and message-integrity.
+
+ // TODO: Add changed-address. (Keep information about three other servers.)
+
+ SendResponse(response, remote_addr);
+}
+
+void StunServer::OnAllocateRequest(
+ StunMessage* msg, const talk_base::SocketAddress& addr) {
+ SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::OnSharedSecretRequest(
+ StunMessage* msg, const talk_base::SocketAddress& addr) {
+ SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::OnSendRequest(StunMessage* msg,
+ const talk_base::SocketAddress& addr) {
+ SendErrorResponse(*msg, addr, 600, "Operation Not Supported");
+}
+
+void StunServer::SendErrorResponse(
+ const StunMessage& msg, const talk_base::SocketAddress& addr,
+ int error_code, const char* error_desc) {
+
+ StunMessage err_msg;
+ err_msg.SetType(GetStunErrorResponseType(msg.type()));
+ err_msg.SetTransactionID(msg.transaction_id());
+
+ StunErrorCodeAttribute* err_code = StunAttribute::CreateErrorCode();
+ err_code->SetErrorClass(error_code / 100);
+ err_code->SetNumber(error_code % 100);
+ err_code->SetReason(error_desc);
+ err_msg.AddAttribute(err_code);
+
+ SendResponse(err_msg, addr);
+}
+
+void StunServer::SendResponse(
+ const StunMessage& msg, const talk_base::SocketAddress& addr) {
+
+ talk_base::ByteBuffer buf;
+ msg.Write(&buf);
+
+ // TODO: Allow response addr attribute if sent from another stun server.
+
+ if (socket_->SendTo(buf.Data(), buf.Length(), addr) < 0)
+ LOG_ERR(LS_ERROR) << "sendto";
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/stunserver.h b/talk/p2p/base/stunserver.h
new file mode 100644
index 0000000..6e51ad1
--- /dev/null
+++ b/talk/p2p/base/stunserver.h
@@ -0,0 +1,77 @@
+/*
+ * 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_P2P_BASE_STUNSERVER_H_
+#define TALK_P2P_BASE_STUNSERVER_H_
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/p2p/base/stun.h"
+
+namespace cricket {
+
+const int STUN_SERVER_PORT = 3478;
+
+class StunServer : public sigslot::has_slots<> {
+ public:
+ // Creates a STUN server, which will listen on the given socket.
+ explicit StunServer(talk_base::AsyncUDPSocket* socket);
+ // Removes the STUN server from the socket and deletes the socket.
+ ~StunServer();
+
+ protected:
+ // Slot for AsyncSocket.PacketRead:
+ void OnPacket(
+ talk_base::AsyncPacketSocket* socket, const char* buf, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ // Handlers for the different types of STUN/TURN requests:
+ void OnBindingRequest(StunMessage* msg,
+ const talk_base::SocketAddress& addr);
+ void OnAllocateRequest(StunMessage* msg,
+ const talk_base::SocketAddress& addr);
+ void OnSharedSecretRequest(StunMessage* msg,
+ const talk_base::SocketAddress& addr);
+ void OnSendRequest(StunMessage* msg,
+ const talk_base::SocketAddress& addr);
+
+ // Sends an error response to the given message back to the user.
+ void SendErrorResponse(
+ const StunMessage& msg, const talk_base::SocketAddress& addr,
+ int error_code, const char* error_desc);
+
+ // Sends the given message to the appropriate destination.
+ void SendResponse(const StunMessage& msg,
+ const talk_base::SocketAddress& addr);
+
+ private:
+ talk_base::scoped_ptr<talk_base::AsyncUDPSocket> socket_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_STUNSERVER_H_
diff --git a/talk/p2p/base/stunserver_main.cc b/talk/p2p/base/stunserver_main.cc
new file mode 100644
index 0000000..e697728
--- /dev/null
+++ b/talk/p2p/base/stunserver_main.cc
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+#ifdef POSIX
+#include <errno.h>
+#endif // POSIX
+
+#include <iostream>
+
+#include "talk/base/host.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/stunserver.h"
+
+using namespace cricket;
+
+int main(int argc, char* argv[]) {
+ if (argc != 2) {
+ std::cerr << "usage: stunserver address" << std::endl;
+ return 1;
+ }
+
+ talk_base::SocketAddress server_addr;
+ if (!server_addr.FromString(argv[1])) {
+ std::cerr << "Unable to parse IP address: " << argv[1];
+ return 1;
+ }
+
+ talk_base::Thread *pthMain = talk_base::Thread::Current();
+
+ talk_base::AsyncUDPSocket* server_socket =
+ talk_base::AsyncUDPSocket::Create(pthMain->socketserver(), server_addr);
+ if (!server_socket) {
+ std::cerr << "Failed to create a UDP socket" << std::endl;
+ return 1;
+ }
+
+ StunServer* server = new StunServer(server_socket);
+
+ std::cout << "Listening at " << server_addr.ToString() << std::endl;
+
+ pthMain->Run();
+
+ delete server;
+ return 0;
+}
diff --git a/talk/p2p/base/tcpport.cc b/talk/p2p/base/tcpport.cc
new file mode 100644
index 0000000..41e04d2
--- /dev/null
+++ b/talk/p2p/base/tcpport.cc
@@ -0,0 +1,262 @@
+/*
+ * 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 "talk/p2p/base/tcpport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+TCPPort::TCPPort(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip,
+ int min_port, int max_port, bool allow_listen)
+ : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
+ incoming_only_(false),
+ allow_listen_(allow_listen),
+ socket_(NULL),
+ error_(0) {
+}
+
+bool TCPPort::Init() {
+ // Treat failure to create or bind a TCP socket as fatal. This
+ // should never happen.
+ socket_ = factory_->CreateServerTcpSocket(
+ talk_base::SocketAddress(ip_, 0), min_port_, max_port_, allow_listen_,
+ false /* ssl */);
+ if (!socket_) {
+ LOG_J(LS_ERROR, this) << "TCP socket creation failed.";
+ return false;
+ }
+ socket_->SignalNewConnection.connect(this, &TCPPort::OnNewConnection);
+ return true;
+}
+
+TCPPort::~TCPPort() {
+ delete socket_;
+}
+
+Connection* TCPPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ // We only support TCP protocols
+ if ((address.protocol() != "tcp") && (address.protocol() != "ssltcp"))
+ return NULL;
+
+ // We can't accept TCP connections incoming on other ports
+ if (origin == ORIGIN_OTHER_PORT)
+ return NULL;
+
+ // Check if we are allowed to make outgoing TCP connections
+ if (incoming_only_ && (origin == ORIGIN_MESSAGE))
+ return NULL;
+
+ // We don't know how to act as an ssl server yet
+ if ((address.protocol() == "ssltcp") && (origin == ORIGIN_THIS_PORT))
+ return NULL;
+
+ TCPConnection* conn = NULL;
+ if (talk_base::AsyncPacketSocket* socket =
+ GetIncoming(address.address(), true)) {
+ socket->SignalReadPacket.disconnect(this);
+ conn = new TCPConnection(this, address, socket);
+ } else {
+ conn = new TCPConnection(this, address);
+ }
+ AddConnection(conn);
+ return conn;
+}
+
+void TCPPort::PrepareAddress() {
+ if (!allow_listen_) {
+ LOG_J(LS_INFO, this) << "Not listening due to firewall restrictions.";
+ }
+ // Note: We still add the address, since otherwise the remote side won't
+ // recognize our incoming TCP connections.
+ bool allocated;
+ talk_base::SocketAddress address = socket_->GetLocalAddress(&allocated);
+ if (allocated) {
+ AddAddress(address, "tcp", true);
+ } else {
+ socket_->SignalAddressReady.connect(this, &TCPPort::OnAddresReady);
+ }
+}
+
+int TCPPort::SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload) {
+ talk_base::AsyncPacketSocket * socket = NULL;
+ if (TCPConnection * conn = static_cast<TCPConnection*>(GetConnection(addr))) {
+ socket = conn->socket();
+ } else {
+ socket = GetIncoming(addr);
+ }
+ if (!socket) {
+ LOG_J(LS_ERROR, this) << "Attempted to send to an unknown destination, "
+ << addr.ToString();
+ return -1; // TODO: Set error_
+ }
+
+ int sent = socket->Send(data, size);
+ if (sent < 0) {
+ error_ = socket->GetError();
+ LOG_J(LS_ERROR, this) << "TCP send of " << size
+ << " bytes failed with error " << error_;
+ }
+ return sent;
+}
+
+int TCPPort::SetOption(talk_base::Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int TCPPort::GetError() {
+ return error_;
+}
+
+void TCPPort::OnNewConnection(talk_base::AsyncPacketSocket* socket,
+ talk_base::AsyncPacketSocket* new_socket) {
+ ASSERT(socket == socket_);
+
+ Incoming incoming;
+ incoming.addr = new_socket->GetRemoteAddress();
+ incoming.socket = new_socket;
+ incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket);
+
+ LOG_J(LS_VERBOSE, this) << "Accepted connection from "
+ << incoming.addr.ToString();
+ incoming_.push_back(incoming);
+}
+
+talk_base::AsyncPacketSocket* TCPPort::GetIncoming(
+ const talk_base::SocketAddress& addr, bool remove) {
+ talk_base::AsyncPacketSocket* socket = NULL;
+ for (std::list<Incoming>::iterator it = incoming_.begin();
+ it != incoming_.end(); ++it) {
+ if (it->addr == addr) {
+ socket = it->socket;
+ if (remove)
+ incoming_.erase(it);
+ break;
+ }
+ }
+ return socket;
+}
+
+void TCPPort::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ Port::OnReadPacket(data, size, remote_addr);
+}
+
+void TCPPort::OnAddresReady(talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& address) {
+ AddAddress(address, "tcp", true);
+}
+
+TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate,
+ talk_base::AsyncPacketSocket* socket)
+ : Connection(port, 0, candidate), socket_(socket), error_(0) {
+ bool outgoing = (socket_ == NULL);
+ if (outgoing) {
+ // TODO: Handle failures here (unlikely since TCP).
+
+ socket_ = port->socket_factory()->CreateClientTcpSocket(
+ talk_base::SocketAddress(port_->network()->ip(), 0),
+ candidate.address(), port->proxy(), port->user_agent(),
+ candidate.protocol() == "ssltcp");
+ if (socket_) {
+ LOG_J(LS_VERBOSE, this) << "Connecting from "
+ << socket_->GetLocalAddress(NULL).ToString()
+ << " to " << candidate.address().ToString();
+ set_connected(false);
+ socket_->SignalConnect.connect(this, &TCPConnection::OnConnect);
+ } else {
+ LOG_J(LS_WARNING, this) << "Failed to create connection to "
+ << candidate.address().ToString();
+ }
+ } else {
+ // Incoming connections should match the network address.
+ ASSERT(socket_->GetLocalAddress(NULL).ip() == port->ip_);
+ }
+
+ if (socket_) {
+ socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket);
+ socket_->SignalClose.connect(this, &TCPConnection::OnClose);
+ }
+}
+
+TCPConnection::~TCPConnection() {
+ delete socket_;
+}
+
+int TCPConnection::Send(const void* data, size_t size) {
+ if (!socket_) {
+ error_ = ENOTCONN;
+ return SOCKET_ERROR;
+ }
+
+ if (write_state() != STATE_WRITABLE) {
+ // TODO: Should STATE_WRITE_TIMEOUT return a non-blocking error?
+ error_ = EWOULDBLOCK;
+ return SOCKET_ERROR;
+ }
+ int sent = socket_->Send(data, size);
+ if (sent < 0) {
+ error_ = socket_->GetError();
+ } else {
+ send_rate_tracker_.Update(sent);
+ }
+ return sent;
+}
+
+int TCPConnection::GetError() {
+ return error_;
+}
+
+void TCPConnection::OnConnect(talk_base::AsyncPacketSocket* socket) {
+ ASSERT(socket == socket_);
+ LOG_J(LS_VERBOSE, this) << "Connection established to "
+ << socket->GetRemoteAddress().ToString();
+ set_connected(true);
+}
+
+void TCPConnection::OnClose(talk_base::AsyncPacketSocket* socket, int error) {
+ ASSERT(socket == socket_);
+ LOG_J(LS_VERBOSE, this) << "Connection closed with error " << error;
+ set_connected(false);
+ set_write_state(STATE_WRITE_TIMEOUT);
+}
+
+void TCPConnection::OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ ASSERT(socket == socket_);
+ Connection::OnReadPacket(data, size);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/tcpport.h b/talk/p2p/base/tcpport.h
new file mode 100644
index 0000000..0de3561
--- /dev/null
+++ b/talk/p2p/base/tcpport.h
@@ -0,0 +1,141 @@
+/*
+ * 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_P2P_BASE_TCPPORT_H_
+#define TALK_P2P_BASE_TCPPORT_H_
+
+#include <string>
+#include <list>
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+class TCPConnection;
+
+extern const std::string LOCAL_PORT_TYPE; // type of TCP ports
+
+// Communicates using a local TCP port.
+//
+// This class is designed to allow subclasses to take advantage of the
+// connection management provided by this class. A subclass should take of all
+// packet sending and preparation, but when a packet is received, it should
+// call this TCPPort::OnReadPacket (3 arg) to dispatch to a connection.
+class TCPPort : public Port {
+ public:
+ static TCPPort* Create(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ uint32 ip, int min_port, int max_port,
+ bool allow_listen) {
+ TCPPort* port = new TCPPort(thread, factory, network,
+ ip, min_port, max_port, allow_listen);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+ virtual ~TCPPort();
+
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+
+ virtual void PrepareAddress();
+
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ protected:
+ TCPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip, int min_port, int max_port,
+ bool allow_listen);
+ bool Init();
+
+ // Handles sending using the local TCP socket.
+ virtual int SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload);
+
+ // Accepts incoming TCP connection.
+ void OnNewConnection(talk_base::AsyncPacketSocket* socket,
+ talk_base::AsyncPacketSocket* new_socket);
+
+ private:
+ struct Incoming {
+ talk_base::SocketAddress addr;
+ talk_base::AsyncPacketSocket* socket;
+ };
+
+ talk_base::AsyncPacketSocket* GetIncoming(
+ const talk_base::SocketAddress& addr, bool remove = false);
+
+ // Receives packet signal from the local TCP Socket.
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ void OnAddresReady(talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& address);
+
+ // TODO: Is this still needed?
+ bool incoming_only_;
+ bool allow_listen_;
+ talk_base::AsyncPacketSocket* socket_;
+ int error_;
+ std::list<Incoming> incoming_;
+
+ friend class TCPConnection;
+};
+
+class TCPConnection : public Connection {
+ public:
+ // Connection is outgoing unless socket is specified
+ TCPConnection(TCPPort* port, const Candidate& candidate,
+ talk_base::AsyncPacketSocket* socket = 0);
+ virtual ~TCPConnection();
+
+ virtual int Send(const void* data, size_t size);
+ virtual int GetError();
+
+ talk_base::AsyncPacketSocket* socket() { return socket_; }
+
+ private:
+ void OnConnect(talk_base::AsyncPacketSocket* socket);
+ void OnClose(talk_base::AsyncPacketSocket* socket, int error);
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ talk_base::AsyncPacketSocket* socket_;
+ int error_;
+
+ friend class TCPPort;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TCPPORT_H_
diff --git a/talk/p2p/base/transport.cc b/talk/p2p/base/transport.cc
new file mode 100644
index 0000000..dc571d4
--- /dev/null
+++ b/talk/p2p/base/transport.cc
@@ -0,0 +1,468 @@
+/*
+ * 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 "talk/p2p/base/transport.h"
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+struct ChannelParams {
+ ChannelParams() : channel(NULL), candidate(NULL) {}
+ explicit ChannelParams(const std::string& name)
+ : name(name), channel(NULL), candidate(NULL) {}
+ ChannelParams(const std::string& name,
+ const std::string& content_type)
+ : name(name), content_type(content_type),
+ channel(NULL), candidate(NULL) {}
+ explicit ChannelParams(cricket::Candidate* candidate) :
+ channel(NULL), candidate(candidate) {
+ name = candidate->name();
+ }
+
+ ~ChannelParams() {
+ delete candidate;
+ }
+
+ std::string name;
+ std::string content_type;
+ cricket::TransportChannelImpl* channel;
+ cricket::Candidate* candidate;
+};
+typedef talk_base::TypedMessageData<ChannelParams*> ChannelMessage;
+
+enum {
+ MSG_CREATECHANNEL = 1,
+ MSG_DESTROYCHANNEL = 2,
+ MSG_DESTROYALLCHANNELS = 3,
+ MSG_CONNECTCHANNELS = 4,
+ MSG_RESETCHANNELS = 5,
+ MSG_ONSIGNALINGREADY = 6,
+ MSG_ONREMOTECANDIDATE = 7,
+ MSG_READSTATE = 8,
+ MSG_WRITESTATE = 9,
+ MSG_REQUESTSIGNALING = 10,
+ MSG_ONCHANNELCANDIDATEREADY = 11,
+ MSG_CONNECTING = 12,
+};
+
+Transport::Transport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ const std::string& type,
+ PortAllocator* allocator)
+ : signaling_thread_(signaling_thread),
+ worker_thread_(worker_thread), type_(type), allocator_(allocator),
+ destroyed_(false), readable_(false), writable_(false),
+ connect_requested_(false), allow_local_ips_(false) {
+}
+
+Transport::~Transport() {
+ ASSERT(signaling_thread_->IsCurrent());
+ ASSERT(destroyed_);
+}
+
+TransportChannelImpl* Transport::CreateChannel(
+ const std::string& name, const std::string& content_type) {
+ ChannelParams params(name, content_type);
+ ChannelMessage msg(¶ms);
+ worker_thread()->Send(this, MSG_CREATECHANNEL, &msg);
+ return msg.data()->channel;
+}
+
+TransportChannelImpl* Transport::CreateChannel_w(
+ const std::string& name, const std::string& content_type) {
+ ASSERT(worker_thread()->IsCurrent());
+
+ TransportChannelImpl* impl = CreateTransportChannel(name, content_type);
+ impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState);
+ impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState);
+ impl->SignalRequestSignaling.connect(
+ this, &Transport::OnChannelRequestSignaling);
+ impl->SignalCandidateReady.connect(this, &Transport::OnChannelCandidateReady);
+
+ talk_base::CritScope cs(&crit_);
+ ASSERT(channels_.find(name) == channels_.end());
+ channels_[name] = impl;
+ destroyed_ = false;
+ if (connect_requested_) {
+ impl->Connect();
+ if (channels_.size() == 1) {
+ // If this is the first channel, then indicate that we have started
+ // connecting.
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+ }
+ return impl;
+}
+
+TransportChannelImpl* Transport::GetChannel(const std::string& name) {
+ talk_base::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(name);
+ return (iter != channels_.end()) ? iter->second : NULL;
+}
+
+bool Transport::HasChannels() {
+ talk_base::CritScope cs(&crit_);
+ return !channels_.empty();
+}
+
+void Transport::DestroyChannel(const std::string& name) {
+ ChannelParams params(name);
+ ChannelMessage msg(¶ms);
+ worker_thread()->Send(this, MSG_DESTROYCHANNEL, &msg);
+}
+
+void Transport::DestroyChannel_w(const std::string& name) {
+ ASSERT(worker_thread()->IsCurrent());
+
+ TransportChannelImpl* impl = NULL;
+ {
+ talk_base::CritScope cs(&crit_);
+ ChannelMap::iterator iter = channels_.find(name);
+ if (iter == channels_.end())
+ return;
+ impl = iter->second;
+ channels_.erase(iter);
+ }
+
+ if (connect_requested_ && channels_.empty()) {
+ // We're no longer attempting to connect.
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+
+ if (impl) {
+ // Check in case the deleted channel was the only non-writable channel.
+ OnChannelWritableState(impl);
+ DestroyTransportChannel(impl);
+ }
+}
+
+void Transport::ConnectChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread()->Send(this, MSG_CONNECTCHANNELS, NULL);
+}
+
+void Transport::ConnectChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+ if (connect_requested_ || channels_.empty())
+ return;
+ connect_requested_ = true;
+ signaling_thread()->Post(
+ this, MSG_ONCHANNELCANDIDATEREADY, NULL);
+ CallChannels_w(&TransportChannelImpl::Connect);
+ if (!channels_.empty()) {
+ signaling_thread()->Post(this, MSG_CONNECTING, NULL);
+ }
+}
+
+void Transport::OnConnecting_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ SignalConnecting(this);
+}
+
+void Transport::DestroyAllChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread()->Send(this, MSG_DESTROYALLCHANNELS, NULL);
+ worker_thread()->Clear(this);
+ signaling_thread()->Clear(this);
+ destroyed_ = true;
+}
+
+void Transport::DestroyAllChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+ std::vector<TransportChannelImpl*> impls;
+ {
+ talk_base::CritScope cs(&crit_);
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ impls.push_back(iter->second);
+ }
+ channels_.clear();
+ }
+
+ for (size_t i = 0; i < impls.size(); ++i)
+ DestroyTransportChannel(impls[i]);
+}
+
+void Transport::ResetChannels() {
+ ASSERT(signaling_thread()->IsCurrent());
+ worker_thread()->Send(this, MSG_RESETCHANNELS, NULL);
+}
+
+void Transport::ResetChannels_w() {
+ ASSERT(worker_thread()->IsCurrent());
+
+ // We are no longer attempting to connect
+ connect_requested_ = false;
+
+ // Clear out the old messages, they aren't relevant
+ talk_base::CritScope cs(&crit_);
+ ready_candidates_.clear();
+
+ // Reset all of the channels
+ CallChannels_w(&TransportChannelImpl::Reset);
+}
+
+void Transport::OnSignalingReady() {
+ ASSERT(signaling_thread()->IsCurrent());
+ if (destroyed_) return;
+
+ worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL);
+
+ // Notify the subclass.
+ OnTransportSignalingReady();
+}
+
+void Transport::CallChannels_w(TransportChannelFunc func) {
+ ASSERT(worker_thread()->IsCurrent());
+ talk_base::CritScope cs(&crit_);
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ ((iter->second)->*func)();
+ }
+}
+
+bool Transport::VerifyCandidate(const Candidate& cand, ParseError* error) {
+ if (cand.address().IsLocalIP() && !allow_local_ips_)
+ return BadParse("candidate has local IP address", error);
+
+ // No address zero.
+ if (cand.address().IsAny()) {
+ return BadParse("candidate has address of zero", error);
+ }
+
+ // Disallow all ports below 1024, except for 80 and 443 on public addresses.
+ int port = cand.address().port();
+ if (port < 1024) {
+ if ((port != 80) && (port != 443))
+ return BadParse(
+ "candidate has port below 1024, but not 80 or 443", error);
+ if (cand.address().IsPrivateIP()) {
+ return BadParse(
+ "candidate has port of 80 or 443 with private IP address", error);
+ }
+ }
+
+ return true;
+}
+
+void Transport::OnRemoteCandidates(const std::vector<Candidate>& candidates) {
+ for (std::vector<Candidate>::const_iterator iter = candidates.begin();
+ iter != candidates.end();
+ ++iter) {
+ OnRemoteCandidate(*iter);
+ }
+}
+
+void Transport::OnRemoteCandidate(const Candidate& candidate) {
+ ASSERT(signaling_thread()->IsCurrent());
+ if (destroyed_) return;
+ if (!HasChannel(candidate.name())) {
+ LOG(LS_WARNING) << "Ignoring candidate for unknown channel "
+ << candidate.name();
+ return;
+ }
+
+ // new candidate deleted when params is deleted
+ ChannelParams* params = new ChannelParams(new Candidate(candidate));
+ ChannelMessage* msg = new ChannelMessage(params);
+ worker_thread()->Post(this, MSG_ONREMOTECANDIDATE, msg);
+}
+
+void Transport::OnRemoteCandidate_w(const Candidate& candidate) {
+ ASSERT(worker_thread()->IsCurrent());
+ ChannelMap::iterator iter = channels_.find(candidate.name());
+ // It's ok for a channel to go away while this message is in transit.
+ if (iter != channels_.end()) {
+ iter->second->OnCandidate(candidate);
+ }
+}
+
+void Transport::OnChannelReadableState(TransportChannel* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ signaling_thread()->Post(this, MSG_READSTATE, NULL);
+}
+
+void Transport::OnChannelReadableState_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ bool readable = GetTransportState_s(true);
+ if (readable_ != readable) {
+ readable_ = readable;
+ SignalReadableState(this);
+ }
+}
+
+void Transport::OnChannelWritableState(TransportChannel* channel) {
+ ASSERT(worker_thread()->IsCurrent());
+ signaling_thread()->Post(this, MSG_WRITESTATE, NULL);
+}
+
+void Transport::OnChannelWritableState_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ bool writable = GetTransportState_s(false);
+ if (writable_ != writable) {
+ writable_ = writable;
+ SignalWritableState(this);
+ }
+}
+
+bool Transport::GetTransportState_s(bool read) {
+ ASSERT(signaling_thread()->IsCurrent());
+ bool result = false;
+ talk_base::CritScope cs(&crit_);
+ for (ChannelMap::iterator iter = channels_.begin();
+ iter != channels_.end();
+ ++iter) {
+ bool b = (read ? iter->second->readable() : iter->second->writable());
+ result = result || b;
+ }
+ return result;
+}
+
+void Transport::OnChannelRequestSignaling() {
+ ASSERT(worker_thread()->IsCurrent());
+ signaling_thread()->Post(this, MSG_REQUESTSIGNALING, NULL);
+}
+
+void Transport::OnChannelRequestSignaling_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ SignalRequestSignaling(this);
+}
+
+void Transport::OnChannelCandidateReady(TransportChannelImpl* channel,
+ const Candidate& candidate) {
+ ASSERT(worker_thread()->IsCurrent());
+ talk_base::CritScope cs(&crit_);
+ ready_candidates_.push_back(candidate);
+
+ // We hold any messages until the client lets us connect.
+ if (connect_requested_) {
+ signaling_thread()->Post(
+ this, MSG_ONCHANNELCANDIDATEREADY, NULL);
+ }
+}
+
+void Transport::OnChannelCandidateReady_s() {
+ ASSERT(signaling_thread()->IsCurrent());
+ ASSERT(connect_requested_);
+
+ std::vector<Candidate> candidates;
+ {
+ talk_base::CritScope cs(&crit_);
+ candidates.swap(ready_candidates_);
+ }
+
+ // we do the deleting of Candidate* here to keep the new above and
+ // delete below close to each other
+ if (!candidates.empty()) {
+ SignalCandidatesReady(this, candidates);
+ }
+}
+
+void Transport::OnMessage(talk_base::Message* msg) {
+ switch (msg->message_id) {
+ case MSG_CREATECHANNEL:
+ {
+ ChannelParams* params = static_cast<ChannelMessage*>(msg->pdata)->data();
+ params->channel = CreateChannel_w(params->name, params->content_type);
+ }
+ break;
+ case MSG_DESTROYCHANNEL:
+ {
+ ChannelParams* params = static_cast<ChannelMessage*>(msg->pdata)->data();
+ DestroyChannel_w(params->name);
+ }
+ break;
+ case MSG_CONNECTCHANNELS:
+ ConnectChannels_w();
+ break;
+ case MSG_RESETCHANNELS:
+ ResetChannels_w();
+ break;
+ case MSG_DESTROYALLCHANNELS:
+ DestroyAllChannels_w();
+ break;
+ case MSG_ONSIGNALINGREADY:
+ CallChannels_w(&TransportChannelImpl::OnSignalingReady);
+ break;
+ case MSG_ONREMOTECANDIDATE:
+ {
+ ChannelMessage* channel_msg = static_cast<ChannelMessage*>(msg->pdata);
+ ChannelParams* params = channel_msg->data();
+ OnRemoteCandidate_w(*(params->candidate));
+ delete params;
+ delete channel_msg;
+ }
+ break;
+ case MSG_CONNECTING:
+ OnConnecting_s();
+ break;
+ case MSG_READSTATE:
+ OnChannelReadableState_s();
+ break;
+ case MSG_WRITESTATE:
+ OnChannelWritableState_s();
+ break;
+ case MSG_REQUESTSIGNALING:
+ OnChannelRequestSignaling_s();
+ break;
+ case MSG_ONCHANNELCANDIDATEREADY:
+ OnChannelCandidateReady_s();
+ break;
+ }
+}
+
+bool TransportParser::ParseAddress(const buzz::XmlElement* elem,
+ const buzz::QName& address_name,
+ const buzz::QName& port_name,
+ talk_base::SocketAddress* address,
+ ParseError* error) {
+ if (!elem->HasAttr(address_name))
+ return BadParse("address does not have " + address_name.LocalPart(), error);
+ if (!elem->HasAttr(port_name))
+ return BadParse("address does not have " + port_name.LocalPart(), error);
+
+ address->SetIP(elem->Attr(address_name));
+ std::istringstream ist(elem->Attr(port_name));
+ int port = 0;
+ ist >> port;
+ address->SetPort(port);
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/transport.h b/talk/p2p/base/transport.h
new file mode 100644
index 0000000..d9a2d94
--- /dev/null
+++ b/talk/p2p/base/transport.h
@@ -0,0 +1,275 @@
+/*
+ * 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.
+ */
+
+// A Transport manages a set of named channels of the same type.
+//
+// Subclasses choose the appropriate class to instantiate for each channel;
+// however, this base class keeps track of the channels by name, watches their
+// state changes (in order to update the manager's state), and forwards
+// requests to begin connecting or to reset to each of the channels.
+//
+// On Threading: Transport performs work on both the signaling and worker
+// threads. For subclasses, the rule is that all signaling related calls will
+// be made on the signaling thread and all channel related calls (including
+// signaling for a channel) will be made on the worker thread. When
+// information needs to be sent between the two threads, this class should do
+// the work (e.g., OnRemoteCandidate).
+//
+// Note: Subclasses must call DestroyChannels() in their own constructors.
+// It is not possible to do so here because the subclass constructor will
+// already have run.
+
+#ifndef TALK_P2P_BASE_TRANSPORT_H_
+#define TALK_P2P_BASE_TRANSPORT_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/candidate.h"
+#include "talk/p2p/base/constants.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace buzz {
+class QName;
+class XmlElement;
+}
+
+namespace cricket {
+
+struct ParseError;
+struct WriteError;
+class PortAllocator;
+class SessionManager;
+class Session;
+class TransportChannel;
+class TransportChannelImpl;
+
+typedef std::vector<buzz::XmlElement*> XmlElements;
+typedef std::vector<Candidate> Candidates;
+
+// Used to parse and serialize (write) transport candidates. For
+// convenience of old code, Transports will implement TransportParser.
+// Parse/Write seems better than Serialize/Deserialize or
+// Create/Translate.
+class TransportParser {
+ public:
+ virtual bool ParseCandidates(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ Candidates* candidates,
+ ParseError* error) = 0;
+ virtual bool WriteCandidates(SignalingProtocol protocol,
+ const Candidates& candidates,
+ XmlElements* candidate_elems,
+ WriteError* error) = 0;
+
+ // Helper function to parse an element describing an address. This
+ // retrieves the IP and port from the given element and verifies
+ // that they look like plausible values.
+ bool ParseAddress(const buzz::XmlElement* elem,
+ const buzz::QName& address_name,
+ const buzz::QName& port_name,
+ talk_base::SocketAddress* address,
+ ParseError* error);
+
+ virtual ~TransportParser() {}
+};
+
+class Transport : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ Transport(talk_base::Thread* signaling_thread,
+ talk_base::Thread* worker_thread,
+ const std::string& type,
+ PortAllocator* allocator);
+ virtual ~Transport();
+
+ // Returns the signaling thread. The app talks to Transport on this thread.
+ talk_base::Thread* signaling_thread() { return signaling_thread_; }
+ // Returns the worker thread. The actual networking is done on this thread.
+ talk_base::Thread* worker_thread() { return worker_thread_; }
+
+ // Returns the type of this transport.
+ const std::string& type() const { return type_; }
+
+ // Returns the port allocator object for this transport.
+ PortAllocator* port_allocator() { return allocator_; }
+
+ // Returns the readable and states of this manager. These bits are the ORs
+ // of the corresponding bits on the managed channels. Each time one of these
+ // states changes, a signal is raised.
+ bool readable() const { return readable_; }
+ bool writable() const { return writable_; }
+ sigslot::signal1<Transport*> SignalReadableState;
+ sigslot::signal1<Transport*> SignalWritableState;
+
+ // Returns whether the client has requested the channels to connect.
+ bool connect_requested() const { return connect_requested_; }
+
+ // Create, destroy, and lookup the channels of this type by their names.
+ TransportChannelImpl* CreateChannel(const std::string& name,
+ const std::string& content_type);
+ // Note: GetChannel may lead to race conditions, since the mutex is not held
+ // after the pointer is returned.
+ TransportChannelImpl* GetChannel(const std::string& name);
+ // Note: HasChannel does not lead to race conditions, unlike GetChannel.
+ bool HasChannel(const std::string& name) {
+ return (NULL != GetChannel(name));
+ }
+ bool HasChannels();
+ void DestroyChannel(const std::string& name);
+
+ // Tells all current and future channels to start connecting. When the first
+ // channel begins connecting, the following signal is raised.
+ void ConnectChannels();
+ sigslot::signal1<Transport*> SignalConnecting;
+
+ // Resets all of the channels back to their initial state. They are no
+ // longer connecting.
+ void ResetChannels();
+
+ // Destroys every channel created so far.
+ void DestroyAllChannels();
+
+ // Before any stanza is sent, the manager will request signaling. Once
+ // signaling is available, the client should call OnSignalingReady. Once
+ // this occurs, the transport (or its channels) can send any waiting stanzas.
+ // OnSignalingReady invokes OnTransportSignalingReady and then forwards this
+ // signal to each channel.
+ sigslot::signal1<Transport*> SignalRequestSignaling;
+ void OnSignalingReady();
+
+ // Handles sending of ready candidates and receiving of remote candidates.
+ sigslot::signal2<Transport*,
+ const std::vector<Candidate>&> SignalCandidatesReady;
+ void OnRemoteCandidates(const std::vector<Candidate>& candidates);
+
+ // If candidate is not acceptable, returns false and sets error.
+ // Call this before calling OnRemoteCandidates.
+ virtual bool VerifyCandidate(const Candidate& candidate,
+ ParseError* error);
+
+ // A transport message has generated an transport-specific error. The
+ // stanza that caused the error is available in session_msg. If false is
+ // returned, the error is considered unrecoverable, and the session is
+ // terminated.
+ // TODO: Make OnTransportError take an abstract data type
+ // rather than an XmlElement. It isn't needed yet, but it might be
+ // later for Jingle compliance.
+ virtual void OnTransportError(const buzz::XmlElement* error) {}
+ sigslot::signal6<Transport*, const buzz::XmlElement*, const buzz::QName&,
+ const std::string&, const std::string&,
+ const buzz::XmlElement*>
+ SignalTransportError;
+
+ sigslot::signal2<Transport*, const std::string&> SignalChannelGone;
+
+ // (For testing purposes only.) This indicates whether we will allow local
+ // IPs (e.g. 127.*) to be used as addresses for P2P.
+ bool allow_local_ips() const { return allow_local_ips_; }
+ void set_allow_local_ips(bool value) { allow_local_ips_ = value; }
+
+ protected:
+ // These are called by Create/DestroyChannel above in order to create or
+ // destroy the appropriate type of channel.
+ virtual TransportChannelImpl* CreateTransportChannel(
+ const std::string& name, const std::string &content_type) = 0;
+ virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0;
+
+ // Informs the subclass that we received the signaling ready message.
+ virtual void OnTransportSignalingReady() {}
+
+ private:
+ typedef std::map<std::string, TransportChannelImpl*> ChannelMap;
+
+ // Called when the state of a channel changes.
+ void OnChannelReadableState(TransportChannel* channel);
+ void OnChannelWritableState(TransportChannel* channel);
+
+ // Called when a channel requests signaling.
+ void OnChannelRequestSignaling();
+
+ // Called when a candidate is ready from remote peer.
+ void OnRemoteCandidate(const Candidate& candidate);
+ // Called when a candidate is ready from channel.
+ void OnChannelCandidateReady(TransportChannelImpl* channel,
+ const Candidate& candidate);
+
+ // Dispatches messages to the appropriate handler (below).
+ void OnMessage(talk_base::Message* msg);
+
+ // These are versions of the above methods that are called only on a
+ // particular thread (s = signaling, w = worker). The above methods post or
+ // send a message to invoke this version.
+ TransportChannelImpl* CreateChannel_w(const std::string& name,
+ const std::string& content_type);
+ void DestroyChannel_w(const std::string& name);
+ void ConnectChannels_w();
+ void ResetChannels_w();
+ void DestroyAllChannels_w();
+ void OnRemoteCandidate_w(const Candidate& candidate);
+ void OnChannelReadableState_s();
+ void OnChannelWritableState_s();
+ void OnChannelRequestSignaling_s();
+ void OnConnecting_s();
+
+ // Helper function that invokes the given function on every channel.
+ typedef void (TransportChannelImpl::* TransportChannelFunc)();
+ void CallChannels_w(TransportChannelFunc func);
+
+ // Computes the OR of the channel's read or write state (argument picks).
+ bool GetTransportState_s(bool read);
+
+ void OnChannelCandidateReady_s();
+
+ talk_base::Thread* signaling_thread_;
+ talk_base::Thread* worker_thread_;
+ std::string type_;
+ PortAllocator* allocator_;
+ bool destroyed_;
+ bool readable_;
+ bool writable_;
+ bool connect_requested_;
+ ChannelMap channels_;
+ // Buffers the ready_candidates so that SignalCanidatesReady can
+ // provide them in multiples.
+ std::vector<Candidate> ready_candidates_;
+ // Protects changes to channels and messages
+ talk_base::CriticalSection crit_;
+ bool allow_local_ips_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(Transport);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TRANSPORT_H_
diff --git a/talk/p2p/base/transportchannel.cc b/talk/p2p/base/transportchannel.cc
new file mode 100644
index 0000000..ba076ac
--- /dev/null
+++ b/talk/p2p/base/transportchannel.cc
@@ -0,0 +1,56 @@
+/*
+ * 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 <sstream>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+std::string TransportChannel::ToString() const {
+ const char READABLE_ABBREV[2] = { '_', 'R' };
+ const char WRITABLE_ABBREV[2] = { '_', 'W' };
+ std::stringstream ss;
+ ss << "Channel[" << name_ << "|" << READABLE_ABBREV[readable_]
+ << WRITABLE_ABBREV[writable_] << "]";
+ return ss.str();
+}
+
+void TransportChannel::set_readable(bool readable) {
+ if (readable_ != readable) {
+ readable_ = readable;
+ SignalReadableState(this);
+ }
+}
+
+void TransportChannel::set_writable(bool writable) {
+ if (writable_ != writable) {
+ writable_ = writable;
+ SignalWritableState(this);
+ }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/transportchannel.h b/talk/p2p/base/transportchannel.h
new file mode 100644
index 0000000..ff252bf
--- /dev/null
+++ b/talk/p2p/base/transportchannel.h
@@ -0,0 +1,110 @@
+/*
+ * 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_P2P_BASE_TRANSPORTCHANNEL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNEL_H_
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+
+namespace cricket {
+
+class P2PTransportChannel;
+
+// A TransportChannel represents one logical stream of packets that are sent
+// between the two sides of a session.
+class TransportChannel: public sigslot::has_slots<> {
+ public:
+ TransportChannel(const std::string& name, const std::string &content_type)
+ : name_(name), content_type_(content_type),
+ readable_(false), writable_(false) {}
+ virtual ~TransportChannel() {}
+
+ // Returns the name of this channel.
+ const std::string& name() const { return name_; }
+ const std::string& content_type() const { return content_type_; }
+
+ // Returns the readable and states of this channel. Each time one of these
+ // states changes, a signal is raised. These states are aggregated by the
+ // TransportManager.
+ bool readable() const { return readable_; }
+ bool writable() const { return writable_; }
+ sigslot::signal1<TransportChannel*> SignalReadableState;
+ sigslot::signal1<TransportChannel*> SignalWritableState;
+
+ // Attempts to send the given packet. The return value is < 0 on failure.
+ virtual int SendPacket(const char *data, size_t len) = 0;
+
+ // Sets a socket option on this channel. Note that not all options are
+ // supported by all transport types.
+ virtual int SetOption(talk_base::Socket::Option opt, int value) = 0;
+
+ // Returns the most recent error that occurred on this channel.
+ virtual int GetError() = 0;
+
+ // This hack is here to allow the SocketMonitor to downcast to the
+ // P2PTransportChannel safely.
+ // TODO: Generalize network monitoring.
+ virtual P2PTransportChannel* GetP2PChannel() { return NULL; }
+
+ // Signalled each time a packet is received on this channel.
+ sigslot::signal3<TransportChannel*, const char*, size_t> SignalReadPacket;
+
+ // This signal occurs when there is a change in the way that packets are
+ // being routed. The address indicates the address of the first hop in the
+ // new route, if this is known. If this cannot be determined or is not well-
+ // defined, then the channel may give an address of 0.
+ sigslot::signal2<TransportChannel*, const talk_base::SocketAddress&>
+ SignalRouteChange;
+
+ // Invoked when the channel is being destroyed.
+ sigslot::signal1<TransportChannel*> SignalDestroyed;
+
+ // Debugging description of this transport channel.
+ std::string ToString() const;
+
+ protected:
+ // Sets the readable state, signaling if necessary.
+ void set_readable(bool readable);
+
+ // Sets the writable state, signaling if necessary.
+ void set_writable(bool writable);
+
+ private:
+ std::string name_;
+ std::string content_type_;
+ bool readable_;
+ bool writable_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannel);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TRANSPORTCHANNEL_H_
diff --git a/talk/p2p/base/transportchannelimpl.h b/talk/p2p/base/transportchannelimpl.h
new file mode 100644
index 0000000..39e4288
--- /dev/null
+++ b/talk/p2p/base/transportchannelimpl.h
@@ -0,0 +1,81 @@
+/*
+ * 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_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+#define TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
+
+#include <string>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace buzz { class XmlElement; }
+
+namespace cricket {
+
+class Transport;
+class Candidate;
+
+// Base class for real implementations of TransportChannel. This includes some
+// methods called only by Transport, which do not need to be exposed to the
+// client.
+class TransportChannelImpl : public TransportChannel {
+ public:
+ TransportChannelImpl(const std::string& name, const std::string& content_type)
+ : TransportChannel(name, content_type) {}
+
+ // Returns the transport that created this channel.
+ virtual Transport* GetTransport() = 0;
+
+ // Begins the process of attempting to make a connection to the other client.
+ virtual void Connect() = 0;
+
+ // Resets this channel back to the initial state (i.e., not connecting).
+ virtual void Reset() = 0;
+
+ // Allows an individual channel to request signaling and be notified when it
+ // is ready. This is useful if the individual named channels have need to
+ // send their own transport-info stanzas.
+ sigslot::signal0<> SignalRequestSignaling;
+ virtual void OnSignalingReady() = 0;
+
+ // Handles sending and receiving of candidates. The Transport
+ // receives the candidates and may forward them to the relevant
+ // channel.
+ //
+ // Note: Since candidates are delivered asynchronously to the
+ // channel, they cannot return an error if the message is invalid.
+ // It is assumed that the Transport will have checked validity
+ // before forwarding.
+ sigslot::signal2<TransportChannelImpl*,
+ const Candidate&> SignalCandidateReady;
+ virtual void OnCandidate(const Candidate& candidate) = 0;
+ private:
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_TRANSPORTCHANNELIMPL_H_
diff --git a/talk/p2p/base/transportchannelproxy.cc b/talk/p2p/base/transportchannelproxy.cc
new file mode 100644
index 0000000..1be9194
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.cc
@@ -0,0 +1,107 @@
+/*
+ * 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 "talk/p2p/base/transportchannelproxy.h"
+#include "talk/base/common.h"
+#include "talk/p2p/base/transport.h"
+#include "talk/p2p/base/transportchannelimpl.h"
+
+namespace cricket {
+
+TransportChannelProxy::TransportChannelProxy(const std::string& name,
+ const std::string &content_type)
+ : TransportChannel(name, content_type), impl_(NULL) {
+}
+
+TransportChannelProxy::~TransportChannelProxy() {
+ if (impl_)
+ impl_->GetTransport()->DestroyChannel(impl_->name());
+}
+
+void TransportChannelProxy::SetImplementation(TransportChannelImpl* impl) {
+ impl_ = impl;
+ impl_->SignalReadableState.connect(
+ this, &TransportChannelProxy::OnReadableState);
+ impl_->SignalWritableState.connect(
+ this, &TransportChannelProxy::OnWritableState);
+ impl_->SignalReadPacket.connect(this, &TransportChannelProxy::OnReadPacket);
+ impl_->SignalRouteChange.connect(this, &TransportChannelProxy::OnRouteChange);
+ for (OptionList::iterator it = pending_options_.begin();
+ it != pending_options_.end();
+ ++it) {
+ impl_->SetOption(it->first, it->second);
+ }
+ pending_options_.clear();
+}
+
+int TransportChannelProxy::SendPacket(const char *data, size_t len) {
+ // Fail if we don't have an impl yet.
+ return (impl_) ? impl_->SendPacket(data, len) : -1;
+}
+
+int TransportChannelProxy::SetOption(talk_base::Socket::Option opt, int value) {
+ if (impl_)
+ return impl_->SetOption(opt, value);
+ pending_options_.push_back(OptionPair(opt, value));
+ return 0;
+}
+
+int TransportChannelProxy::GetError() {
+ ASSERT(impl_ != NULL); // should not be used until channel is writable
+ return impl_->GetError();
+}
+
+P2PTransportChannel* TransportChannelProxy::GetP2PChannel() {
+ if (impl_) {
+ return impl_->GetP2PChannel();
+ }
+ return NULL;
+}
+
+void TransportChannelProxy::OnReadableState(TransportChannel* channel) {
+ ASSERT(channel == impl_);
+ set_readable(impl_->readable());
+}
+
+void TransportChannelProxy::OnWritableState(TransportChannel* channel) {
+ ASSERT(channel == impl_);
+ set_writable(impl_->writable());
+}
+
+void TransportChannelProxy::OnReadPacket(TransportChannel* channel,
+ const char* data, size_t size) {
+ ASSERT(channel == impl_);
+ SignalReadPacket(this, data, size);
+}
+
+void TransportChannelProxy::OnRouteChange(TransportChannel* channel,
+ const talk_base::SocketAddress& address) {
+ ASSERT(channel == impl_);
+ SignalRouteChange(this, address);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/transportchannelproxy.h b/talk/p2p/base/transportchannelproxy.h
new file mode 100644
index 0000000..4601185
--- /dev/null
+++ b/talk/p2p/base/transportchannelproxy.h
@@ -0,0 +1,78 @@
+/*
+ * 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 _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+#define _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/base/transportchannel.h"
+
+namespace cricket {
+
+class TransportChannelImpl;
+
+// Proxies calls between the client and the transport channel implementation.
+// This is needed because clients are allowed to create channels before the
+// network negotiation is complete. Hence, we create a proxy up front, and
+// when negotiation completes, connect the proxy to the implementaiton.
+class TransportChannelProxy: public TransportChannel {
+ public:
+ TransportChannelProxy(const std::string& name, const std::string &content_type);
+ virtual ~TransportChannelProxy();
+
+ TransportChannelImpl* impl() const { return impl_; }
+
+ // Sets the implementation to which we will proxy.
+ void SetImplementation(TransportChannelImpl* impl);
+
+ // Implementation of the TransportChannel interface. These simply forward to
+ // the implementation.
+ virtual int SendPacket(const char *data, size_t len);
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+ virtual P2PTransportChannel* GetP2PChannel();
+
+ private:
+ typedef std::pair<talk_base::Socket::Option, int> OptionPair;
+ typedef std::vector<OptionPair> OptionList;
+ TransportChannelImpl* impl_;
+ OptionList pending_options_;
+
+ // Catch signals from the implementation channel. These just forward to the
+ // client (after updating our state to match).
+ void OnReadableState(TransportChannel* channel);
+ void OnWritableState(TransportChannel* channel);
+ void OnReadPacket(TransportChannel* channel, const char* data, size_t size);
+ void OnRouteChange(TransportChannel* channel, const talk_base::SocketAddress& address);
+
+ DISALLOW_EVIL_CONSTRUCTORS(TransportChannelProxy);
+};
+
+} // namespace cricket
+
+#endif // _CRICKET_P2P_BASE_TRANSPORTCHANNELPROXY_H_
diff --git a/talk/p2p/base/udpport.cc b/talk/p2p/base/udpport.cc
new file mode 100644
index 0000000..28cf5d2
--- /dev/null
+++ b/talk/p2p/base/udpport.cc
@@ -0,0 +1,117 @@
+/*
+ * 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 "talk/p2p/base/udpport.h"
+
+#include "talk/base/asyncpacketsocket.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/common.h"
+
+namespace cricket {
+
+const std::string LOCAL_PORT_TYPE("local");
+
+UDPPort::UDPPort(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ uint32 ip, int min_port, int max_port)
+ : Port(thread, LOCAL_PORT_TYPE, factory, network, ip, min_port, max_port),
+ socket_(NULL),
+ error_(0) {
+}
+
+bool UDPPort::Init() {
+ socket_ = factory_->CreateUdpSocket(
+ talk_base::SocketAddress(ip_, 0), min_port_, max_port_);
+ if (!socket_) {
+ LOG_J(LS_WARNING, this) << "UDP socket creation failed";
+ return false;
+ }
+ socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacket);
+ return true;
+}
+
+UDPPort::~UDPPort() {
+ delete socket_;
+}
+
+void UDPPort::PrepareAddress() {
+ bool allocated;
+ talk_base::SocketAddress address = socket_->GetLocalAddress(&allocated);
+ if (allocated) {
+ AddAddress(address, "udp", true);
+ } else {
+ socket_->SignalAddressReady.connect(this, &UDPPort::OnAddresReady);
+ }
+}
+
+Connection* UDPPort::CreateConnection(const Candidate& address,
+ CandidateOrigin origin) {
+ if (address.protocol() != "udp")
+ return NULL;
+
+ Connection* conn = new ProxyConnection(this, 0, address);
+ AddConnection(conn);
+ return conn;
+}
+
+int UDPPort::SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& addr, bool payload) {
+ int sent = socket_->SendTo(data, size, addr);
+ if (sent < 0) {
+ error_ = socket_->GetError();
+ LOG_J(LS_ERROR, this) << "UDP send of " << size
+ << " bytes failed with error " << error_;
+ }
+ return sent;
+}
+
+int UDPPort::SetOption(talk_base::Socket::Option opt, int value) {
+ return socket_->SetOption(opt, value);
+}
+
+int UDPPort::GetError() {
+ return error_;
+}
+
+void UDPPort::OnAddresReady(talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& address) {
+ AddAddress(address, "udp", true);
+}
+
+void UDPPort::OnReadPacket(
+ talk_base::AsyncPacketSocket* socket, const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr) {
+ ASSERT(socket == socket_);
+ if (Connection* conn = GetConnection(remote_addr)) {
+ conn->OnReadPacket(data, size);
+ } else {
+ Port::OnReadPacket(data, size, remote_addr);
+ }
+}
+
+} // namespace cricket
diff --git a/talk/p2p/base/udpport.h b/talk/p2p/base/udpport.h
new file mode 100644
index 0000000..f7588a6
--- /dev/null
+++ b/talk/p2p/base/udpport.h
@@ -0,0 +1,93 @@
+/*
+ * 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_P2P_BASE_UDPPORT_H_
+#define TALK_P2P_BASE_UDPPORT_H_
+
+#include <string>
+
+#include "talk/p2p/base/port.h"
+
+namespace talk_base {
+class Thread;
+class Network;
+class SocketAddress;
+}
+
+namespace cricket {
+
+extern const std::string LOCAL_PORT_TYPE; // type of UDP ports
+
+// Communicates using a local UDP port.
+class UDPPort : public Port {
+ public:
+ static UDPPort* Create(talk_base::Thread* thread,
+ talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network,
+ uint32 ip, int min_port, int max_port) {
+ UDPPort* port = new UDPPort(thread, factory, network,
+ ip, min_port, max_port);
+ if (!port->Init()) {
+ delete port;
+ port = NULL;
+ }
+ return port;
+ }
+ virtual ~UDPPort();
+
+ virtual void PrepareAddress();
+ virtual Connection* CreateConnection(const Candidate& address,
+ CandidateOrigin origin);
+
+ virtual int SetOption(talk_base::Socket::Option opt, int value);
+ virtual int GetError();
+
+ protected:
+ UDPPort(talk_base::Thread* thread, talk_base::PacketSocketFactory* factory,
+ talk_base::Network* network, uint32 ip, int min_port, int max_port);
+ bool Init();
+
+ // Handles sending using the local UDP socket.
+ virtual int SendTo(const void* data, size_t size,
+ const talk_base::SocketAddress& remote_addr, bool payload);
+
+ void OnAddresReady(talk_base::AsyncPacketSocket* socket,
+ const talk_base::SocketAddress& address);
+
+ // Dispatches the given packet to the port or connection as appropriate.
+ void OnReadPacket(talk_base::AsyncPacketSocket* socket,
+ const char* data, size_t size,
+ const talk_base::SocketAddress& remote_addr);
+
+ private:
+ talk_base::AsyncPacketSocket* socket_;
+ int error_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_BASE_UDPPORT_H_
diff --git a/talk/p2p/client/basicportallocator.cc b/talk/p2p/client/basicportallocator.cc
new file mode 100644
index 0000000..c023e9e
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.cc
@@ -0,0 +1,802 @@
+/*
+ * 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 <string>
+#include <vector>
+
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/host.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/client/basicportallocator.h"
+#include "talk/p2p/base/common.h"
+#include "talk/p2p/base/port.h"
+#include "talk/p2p/base/relayport.h"
+#include "talk/p2p/base/stunport.h"
+#include "talk/p2p/base/tcpport.h"
+#include "talk/p2p/base/udpport.h"
+
+using talk_base::CreateRandomId;
+using talk_base::CreateRandomString;
+
+namespace {
+
+const uint32 MSG_CONFIG_START = 1;
+const uint32 MSG_CONFIG_READY = 2;
+const uint32 MSG_ALLOCATE = 3;
+const uint32 MSG_ALLOCATION_PHASE = 4;
+const uint32 MSG_SHAKE = 5;
+
+const uint32 ALLOCATE_DELAY = 250;
+const uint32 ALLOCATION_STEP_DELAY = 1 * 1000;
+
+const int PHASE_UDP = 0;
+const int PHASE_RELAY = 1;
+const int PHASE_TCP = 2;
+const int PHASE_SSLTCP = 3;
+const int kNumPhases = 4;
+
+const float PREF_LOCAL_UDP = 1.0f;
+const float PREF_LOCAL_STUN = 0.9f;
+const float PREF_LOCAL_TCP = 0.8f;
+const float PREF_RELAY = 0.5f;
+
+// Modifiers of the above constants
+const float RELAY_PRIMARY_PREF_MODIFIER = 0.0f;
+const float RELAY_BACKUP_PREF_MODIFIER = -0.2f;
+
+// Returns the phase in which a given local candidate (or rather, the port that
+// gave rise to that local candidate) would have been created.
+int LocalCandidateToPhase(const cricket::Candidate& candidate) {
+ cricket::ProtocolType proto;
+ bool result = cricket::StringToProto(candidate.protocol().c_str(), &proto);
+ if (result) {
+ if (candidate.type() == cricket::LOCAL_PORT_TYPE) {
+ switch (proto) {
+ case cricket::PROTO_UDP: return PHASE_UDP;
+ case cricket::PROTO_TCP: return PHASE_TCP;
+ default: ASSERT(false);
+ }
+ } else if (candidate.type() == cricket::STUN_PORT_TYPE) {
+ return PHASE_UDP;
+ } else if (candidate.type() == cricket::RELAY_PORT_TYPE) {
+ switch (proto) {
+ case cricket::PROTO_UDP: return PHASE_RELAY;
+ case cricket::PROTO_TCP: return PHASE_TCP;
+ case cricket::PROTO_SSLTCP: return PHASE_SSLTCP;
+ default: ASSERT(false);
+ }
+ } else {
+ ASSERT(false);
+ }
+ } else {
+ ASSERT(false);
+ }
+ return PHASE_UDP; // reached only with assert failure
+}
+
+const int SHAKE_MIN_DELAY = 45 * 1000; // 45 seconds
+const int SHAKE_MAX_DELAY = 90 * 1000; // 90 seconds
+
+int ShakeDelay() {
+ int range = SHAKE_MAX_DELAY - SHAKE_MIN_DELAY + 1;
+ return SHAKE_MIN_DELAY + CreateRandomId() % range;
+}
+
+} // namespace
+
+namespace cricket {
+
+const uint32 DISABLE_ALL_PHASES =
+ PORTALLOCATOR_DISABLE_UDP
+ | PORTALLOCATOR_DISABLE_TCP
+ | PORTALLOCATOR_DISABLE_STUN
+ | PORTALLOCATOR_DISABLE_RELAY;
+
+// Performs the allocation of ports, in a sequenced (timed) manner, for a given
+// network and IP address.
+class AllocationSequence : public talk_base::MessageHandler {
+ public:
+ AllocationSequence(BasicPortAllocatorSession* session,
+ talk_base::Network* network,
+ PortConfiguration* config,
+ uint32 flags);
+ ~AllocationSequence();
+
+ // Disables the phases for a new sequence that this one already covers for an
+ // equivalent network setup.
+ void DisableEquivalentPhases(talk_base::Network* network,
+ PortConfiguration* config, uint32* flags);
+
+ // Starts and stops the sequence. When started, it will continue allocating
+ // new ports on its own timed schedule.
+ void Start();
+ void Stop();
+
+ // MessageHandler
+ void OnMessage(talk_base::Message* msg);
+
+ void EnableProtocol(ProtocolType proto);
+ bool ProtocolEnabled(ProtocolType proto) const;
+
+ private:
+ typedef std::vector<ProtocolType> ProtocolList;
+
+ void CreateUDPPorts();
+ void CreateTCPPorts();
+ void CreateStunPorts();
+ void CreateRelayPorts();
+
+ BasicPortAllocatorSession* session_;
+ talk_base::Network* network_;
+ uint32 ip_;
+ PortConfiguration* config_;
+ bool running_;
+ int step_;
+ int step_of_phase_[kNumPhases];
+ uint32 flags_;
+ ProtocolList protocols_;
+};
+
+
+// BasicPortAllocator
+
+BasicPortAllocator::BasicPortAllocator(
+ talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory)
+ : network_manager_(network_manager),
+ socket_factory_(socket_factory),
+ best_writable_phase_(-1),
+ allow_tcp_listen_(true) {
+ ASSERT(socket_factory_ != NULL);
+}
+
+BasicPortAllocator::BasicPortAllocator(
+ talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const talk_base::SocketAddress& stun_address,
+ const talk_base::SocketAddress& relay_address_udp,
+ const talk_base::SocketAddress& relay_address_tcp,
+ const talk_base::SocketAddress& relay_address_ssl)
+ : network_manager_(network_manager),
+ socket_factory_(socket_factory),
+ stun_address_(stun_address),
+ relay_address_udp_(relay_address_udp),
+ relay_address_tcp_(relay_address_tcp),
+ relay_address_ssl_(relay_address_ssl),
+ best_writable_phase_(-1),
+ allow_tcp_listen_(true) {
+}
+
+BasicPortAllocator::~BasicPortAllocator() {
+}
+
+int BasicPortAllocator::best_writable_phase() const {
+ // If we are configured with an HTTP proxy, the best bet is to use the relay
+ if ((best_writable_phase_ == -1)
+ && ((proxy().type == talk_base::PROXY_HTTPS)
+ || (proxy().type == talk_base::PROXY_UNKNOWN))) {
+ return PHASE_RELAY;
+ }
+ return best_writable_phase_;
+}
+
+PortAllocatorSession *BasicPortAllocator::CreateSession(
+ const std::string &name, const std::string &session_type) {
+ return new BasicPortAllocatorSession(this, name, session_type);
+}
+
+void BasicPortAllocator::AddWritablePhase(int phase) {
+ if ((best_writable_phase_ == -1) || (phase < best_writable_phase_))
+ best_writable_phase_ = phase;
+}
+
+// BasicPortAllocatorSession
+BasicPortAllocatorSession::BasicPortAllocatorSession(
+ BasicPortAllocator *allocator,
+ const std::string &name,
+ const std::string &session_type)
+ : PortAllocatorSession(allocator->flags()), allocator_(allocator),
+ name_(name), session_type_(session_type), network_thread_(NULL),
+ allocation_started_(false), running_(false) {
+}
+
+BasicPortAllocatorSession::~BasicPortAllocatorSession() {
+ if (network_thread_ != NULL)
+ network_thread_->Clear(this);
+
+ std::vector<PortData>::iterator it;
+ for (it = ports_.begin(); it != ports_.end(); it++)
+ delete it->port;
+
+ for (uint32 i = 0; i < configs_.size(); ++i)
+ delete configs_[i];
+
+ for (uint32 i = 0; i < sequences_.size(); ++i)
+ delete sequences_[i];
+}
+
+void BasicPortAllocatorSession::GetInitialPorts() {
+ network_thread_ = talk_base::Thread::Current();
+
+ network_thread_->Post(this, MSG_CONFIG_START);
+
+ if (flags() & PORTALLOCATOR_ENABLE_SHAKER)
+ network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+void BasicPortAllocatorSession::StartGetAllPorts() {
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ running_ = true;
+ if (allocation_started_)
+ network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE);
+ for (uint32 i = 0; i < sequences_.size(); ++i)
+ sequences_[i]->Start();
+ for (size_t i = 0; i < ports_.size(); ++i)
+ ports_[i].port->Start();
+}
+
+void BasicPortAllocatorSession::StopGetAllPorts() {
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ running_ = false;
+ network_thread_->Clear(this, MSG_ALLOCATE);
+ for (uint32 i = 0; i < sequences_.size(); ++i)
+ sequences_[i]->Stop();
+}
+
+void BasicPortAllocatorSession::OnMessage(talk_base::Message *message) {
+ switch (message->message_id) {
+ case MSG_CONFIG_START:
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ GetPortConfigurations();
+ break;
+
+ case MSG_CONFIG_READY:
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ OnConfigReady(static_cast<PortConfiguration*>(message->pdata));
+ break;
+
+ case MSG_ALLOCATE:
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ OnAllocate();
+ break;
+
+ case MSG_SHAKE:
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ OnShake();
+ break;
+
+ default:
+ ASSERT(false);
+ }
+}
+
+void BasicPortAllocatorSession::GetPortConfigurations() {
+ PortConfiguration* config = new PortConfiguration(allocator_->stun_address(),
+ CreateRandomString(16),
+ CreateRandomString(16),
+ "");
+ PortConfiguration::PortList ports;
+ if (!allocator_->relay_address_udp().IsAny())
+ ports.push_back(ProtocolAddress(
+ allocator_->relay_address_udp(), PROTO_UDP));
+ if (!allocator_->relay_address_tcp().IsAny())
+ ports.push_back(ProtocolAddress(
+ allocator_->relay_address_tcp(), PROTO_TCP));
+ if (!allocator_->relay_address_ssl().IsAny())
+ ports.push_back(ProtocolAddress(
+ allocator_->relay_address_ssl(), PROTO_SSLTCP));
+ config->AddRelay(ports, RELAY_PRIMARY_PREF_MODIFIER);
+
+ ConfigReady(config);
+}
+
+void BasicPortAllocatorSession::ConfigReady(PortConfiguration* config) {
+ network_thread_->Post(this, MSG_CONFIG_READY, config);
+}
+
+// Adds a configuration to the list.
+void BasicPortAllocatorSession::OnConfigReady(PortConfiguration* config) {
+ if (config)
+ configs_.push_back(config);
+
+ AllocatePorts();
+}
+
+void BasicPortAllocatorSession::AllocatePorts() {
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ network_thread_->Post(this, MSG_ALLOCATE);
+}
+
+// For each network, see if we have a sequence that covers it already. If not,
+// create a new sequence to create the appropriate ports.
+void BasicPortAllocatorSession::OnAllocate() {
+ std::vector<talk_base::Network*> networks;
+
+ if (!allocator_->network_manager()->GetNetworks(&networks)) {
+ LOG(LS_ERROR) << "Failed to enumerate networks";
+ } else if (networks.empty()) {
+ LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated";
+ } else {
+ for (uint32 i = 0; i < networks.size(); ++i) {
+ PortConfiguration* config = NULL;
+ if (configs_.size() > 0)
+ config = configs_.back();
+
+ uint32 sequence_flags = flags();
+
+ // Disables phases that are not specified in this config.
+ if (!config || config->stun_address.IsNil()) {
+ // No STUN ports specified in this config.
+ sequence_flags |= PORTALLOCATOR_DISABLE_STUN;
+ }
+ if (!config || config->relays.empty()) {
+ // No relay ports specified in this config.
+ sequence_flags |= PORTALLOCATOR_DISABLE_RELAY;
+ }
+
+ // Disable phases that would only create ports equivalent to ones that we
+ // have already made.
+ DisableEquivalentPhases(networks[i], config, &sequence_flags);
+
+ if ((sequence_flags & DISABLE_ALL_PHASES) == DISABLE_ALL_PHASES) {
+ // New AllocationSequence would have nothing to do, so don't make it.
+ continue;
+ }
+
+ AllocationSequence* sequence =
+ new AllocationSequence(this, networks[i], config, sequence_flags);
+ if (running_)
+ sequence->Start();
+
+ sequences_.push_back(sequence);
+ }
+ }
+
+ allocation_started_ = true;
+ if (running_)
+ network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE);
+}
+
+void BasicPortAllocatorSession::DisableEquivalentPhases(
+ talk_base::Network* network, PortConfiguration* config, uint32* flags) {
+ for (uint32 i = 0; i < sequences_.size() &&
+ (*flags & DISABLE_ALL_PHASES) != DISABLE_ALL_PHASES; ++i) {
+ sequences_[i]->DisableEquivalentPhases(network, config, flags);
+ }
+}
+
+void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
+ AllocationSequence * seq,
+ float pref,
+ bool prepare_address) {
+ if (!port)
+ return;
+
+ port->set_name(name_);
+ port->set_preference(pref);
+ port->set_generation(generation());
+ if (allocator_->proxy().type != talk_base::PROXY_NONE)
+ port->set_proxy(allocator_->user_agent(), allocator_->proxy());
+
+ PortData data;
+ data.port = port;
+ data.sequence = seq;
+ data.ready = false;
+ ports_.push_back(data);
+
+ port->SignalAddressReady.connect(this,
+ &BasicPortAllocatorSession::OnAddressReady);
+ port->SignalConnectionCreated.connect(this,
+ &BasicPortAllocatorSession::OnConnectionCreated);
+ port->SignalDestroyed.connect(this,
+ &BasicPortAllocatorSession::OnPortDestroyed);
+ LOG_J(LS_INFO, port) << "Added port to allocator";
+
+ if (prepare_address)
+ port->PrepareAddress();
+ if (running_)
+ port->Start();
+}
+
+void BasicPortAllocatorSession::OnAddressReady(Port *port) {
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ std::vector<PortData>::iterator it
+ = std::find(ports_.begin(), ports_.end(), port);
+ ASSERT(it != ports_.end());
+ if (it->ready)
+ return;
+ it->ready = true;
+ SignalPortReady(this, port);
+
+ // Only accumulate the candidates whose protocol has been enabled
+ std::vector<Candidate> candidates;
+ const std::vector<Candidate>& potentials = port->candidates();
+ for (size_t i = 0; i < potentials.size(); ++i) {
+ ProtocolType pvalue;
+ if (!StringToProto(potentials[i].protocol().c_str(), &pvalue))
+ continue;
+ if (it->sequence->ProtocolEnabled(pvalue)) {
+ candidates.push_back(potentials[i]);
+ }
+ }
+ if (!candidates.empty()) {
+ SignalCandidatesReady(this, candidates);
+ }
+}
+
+void BasicPortAllocatorSession::OnProtocolEnabled(AllocationSequence * seq,
+ ProtocolType proto) {
+ std::vector<Candidate> candidates;
+ for (std::vector<PortData>::iterator it = ports_.begin();
+ it != ports_.end(); ++it) {
+ if (!it->ready || (it->sequence != seq))
+ continue;
+
+ const std::vector<Candidate>& potentials = it->port->candidates();
+ for (size_t i = 0; i < potentials.size(); ++i) {
+ ProtocolType pvalue;
+ if (!StringToProto(potentials[i].protocol().c_str(), &pvalue))
+ continue;
+ if (pvalue == proto) {
+ candidates.push_back(potentials[i]);
+ }
+ }
+ }
+ if (!candidates.empty()) {
+ SignalCandidatesReady(this, candidates);
+ }
+}
+
+void BasicPortAllocatorSession::OnPortDestroyed(Port* port) {
+ ASSERT(talk_base::Thread::Current() == network_thread_);
+ std::vector<PortData>::iterator iter =
+ std::find(ports_.begin(), ports_.end(), port);
+ ASSERT(iter != ports_.end());
+ ports_.erase(iter);
+
+ LOG_J(LS_INFO, port) << "Removed port from allocator ("
+ << static_cast<int>(ports_.size()) << " remaining)";
+}
+
+void BasicPortAllocatorSession::OnConnectionCreated(Port* port,
+ Connection* conn) {
+ conn->SignalStateChange.connect(this,
+ &BasicPortAllocatorSession::OnConnectionStateChange);
+}
+
+void BasicPortAllocatorSession::OnConnectionStateChange(Connection* conn) {
+ if (conn->write_state() == Connection::STATE_WRITABLE)
+ allocator_->AddWritablePhase(
+ LocalCandidateToPhase(conn->local_candidate()));
+}
+
+void BasicPortAllocatorSession::OnShake() {
+ LOG(INFO) << ">>>>> SHAKE <<<<< >>>>> SHAKE <<<<< >>>>> SHAKE <<<<<";
+
+ std::vector<Port*> ports;
+ std::vector<Connection*> connections;
+
+ for (size_t i = 0; i < ports_.size(); ++i) {
+ if (ports_[i].ready)
+ ports.push_back(ports_[i].port);
+ }
+
+ for (size_t i = 0; i < ports.size(); ++i) {
+ Port::AddressMap::const_iterator iter;
+ for (iter = ports[i]->connections().begin();
+ iter != ports[i]->connections().end();
+ ++iter) {
+ connections.push_back(iter->second);
+ }
+ }
+
+ LOG(INFO) << ">>>>> Destroying " << ports.size() << " ports and "
+ << connections.size() << " connections";
+
+ for (size_t i = 0; i < connections.size(); ++i)
+ connections[i]->Destroy();
+
+ if (running_ || (ports.size() > 0) || (connections.size() > 0))
+ network_thread_->PostDelayed(ShakeDelay(), this, MSG_SHAKE);
+}
+
+// AllocationSequence
+
+AllocationSequence::AllocationSequence(BasicPortAllocatorSession* session,
+ talk_base::Network* network,
+ PortConfiguration* config,
+ uint32 flags)
+ : session_(session), network_(network), ip_(network->ip()), config_(config),
+ running_(false), step_(0), flags_(flags) {
+ // All of the phases up until the best-writable phase so far run in step 0.
+ // The other phases follow sequentially in the steps after that. If there is
+ // no best-writable so far, then only phase 0 occurs in step 0.
+ int last_phase_in_step_zero =
+ talk_base::_max(0, session->allocator()->best_writable_phase());
+ for (int phase = 0; phase < kNumPhases; ++phase)
+ step_of_phase_[phase] = talk_base::_max(0, phase - last_phase_in_step_zero);
+
+ // Immediately perform phase 0.
+ OnMessage(NULL);
+}
+
+AllocationSequence::~AllocationSequence() {
+ session_->network_thread()->Clear(this);
+}
+
+void AllocationSequence::DisableEquivalentPhases(talk_base::Network* network,
+ PortConfiguration* config, uint32* flags) {
+ if (!((network == network_) && (ip_ == network->ip()))) {
+ // Different network setup; nothing is equivalent.
+ return;
+ }
+
+ // Else turn off the stuff that we've already got covered.
+
+ // Every config implicitly specifies local, so turn that off right away.
+ *flags |= PORTALLOCATOR_DISABLE_UDP;
+ *flags |= PORTALLOCATOR_DISABLE_TCP;
+
+ if (config_ && config) {
+ if (config_->stun_address == config->stun_address) {
+ // Already got this STUN server covered.
+ *flags |= PORTALLOCATOR_DISABLE_STUN;
+ }
+ if (!config_->relays.empty()) {
+ // Already got relays covered.
+ // NOTE: This will even skip a _different_ set of relay servers if we
+ // were to be given one, but that never happens in our codebase. Should
+ // probably get rid of the list in PortConfiguration and just keep a
+ // single relay server in each one.
+ *flags |= PORTALLOCATOR_DISABLE_RELAY;
+ }
+ }
+}
+
+void AllocationSequence::Start() {
+ running_ = true;
+ session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY,
+ this,
+ MSG_ALLOCATION_PHASE);
+}
+
+void AllocationSequence::Stop() {
+ running_ = false;
+ session_->network_thread()->Clear(this, MSG_ALLOCATION_PHASE);
+}
+
+void AllocationSequence::OnMessage(talk_base::Message* msg) {
+ ASSERT(talk_base::Thread::Current() == session_->network_thread());
+ if (msg)
+ ASSERT(msg->message_id == MSG_ALLOCATION_PHASE);
+
+ const char* const PHASE_NAMES[kNumPhases] = {
+ "Udp", "Relay", "Tcp", "SslTcp"
+ };
+
+ // Perform all of the phases in the current step.
+ for (int phase = 0; phase < kNumPhases; phase++) {
+ if (step_of_phase_[phase] != step_)
+ continue;
+
+ LOG_J(LS_INFO, network_) << "Allocation Phase=" << PHASE_NAMES[phase]
+ << " (Step=" << step_ << ")";
+
+ switch (phase) {
+ case PHASE_UDP:
+ CreateUDPPorts();
+ CreateStunPorts();
+ EnableProtocol(PROTO_UDP);
+ break;
+
+ case PHASE_RELAY:
+ CreateRelayPorts();
+ break;
+
+ case PHASE_TCP:
+ CreateTCPPorts();
+ EnableProtocol(PROTO_TCP);
+ break;
+
+ case PHASE_SSLTCP:
+ EnableProtocol(PROTO_SSLTCP);
+ break;
+
+ default:
+ ASSERT(false);
+ }
+ }
+
+ // TODO: use different delays for each stage
+ step_ += 1;
+ if (running_) {
+ session_->network_thread()->PostDelayed(ALLOCATION_STEP_DELAY,
+ this,
+ MSG_ALLOCATION_PHASE);
+ }
+}
+
+void AllocationSequence::EnableProtocol(ProtocolType proto) {
+ if (!ProtocolEnabled(proto)) {
+ protocols_.push_back(proto);
+ session_->OnProtocolEnabled(this, proto);
+ }
+}
+
+bool AllocationSequence::ProtocolEnabled(ProtocolType proto) const {
+ for (ProtocolList::const_iterator it = protocols_.begin();
+ it != protocols_.end(); ++it) {
+ if (*it == proto)
+ return true;
+ }
+ return false;
+}
+
+void AllocationSequence::CreateUDPPorts() {
+ if (flags_ & PORTALLOCATOR_DISABLE_UDP) {
+ LOG(LS_VERBOSE) << "AllocationSequence: UDP ports disabled, skipping.";
+ return;
+ }
+
+ Port* port = UDPPort::Create(session_->network_thread(),
+ session_->allocator()->socket_factory(),
+ network_, ip_,
+ session_->allocator()->min_port(),
+ session_->allocator()->max_port());
+ if (port)
+ session_->AddAllocatedPort(port, this, PREF_LOCAL_UDP);
+}
+
+void AllocationSequence::CreateTCPPorts() {
+ if (flags_ & PORTALLOCATOR_DISABLE_TCP) {
+ LOG(LS_VERBOSE) << "AllocationSequence: TCP ports disabled, skipping.";
+ return;
+ }
+
+ Port* port = TCPPort::Create(session_->network_thread(),
+ session_->allocator()->socket_factory(),
+ network_, ip_,
+ session_->allocator()->min_port(),
+ session_->allocator()->max_port(),
+ session_->allocator()->allow_tcp_listen());
+ if (port)
+ session_->AddAllocatedPort(port, this, PREF_LOCAL_TCP);
+}
+
+void AllocationSequence::CreateStunPorts() {
+ if (flags_ & PORTALLOCATOR_DISABLE_STUN) {
+ LOG(LS_VERBOSE) << "AllocationSequence: STUN ports disabled, skipping.";
+ return;
+ }
+
+ // If BasicPortAllocatorSession::OnAllocate left STUN ports enabled then we
+ // ought to have an address for them here.
+ ASSERT(config_ && !config_->stun_address.IsNil());
+ if (!(config_ && !config_->stun_address.IsNil())) {
+ LOG(LS_WARNING)
+ << "AllocationSequence: No STUN server configured, skipping.";
+ return;
+ }
+
+ Port* port = StunPort::Create(session_->network_thread(),
+ session_->allocator()->socket_factory(),
+ network_, ip_,
+ session_->allocator()->min_port(),
+ session_->allocator()->max_port(),
+ config_->stun_address);
+ if (port)
+ session_->AddAllocatedPort(port, this, PREF_LOCAL_STUN);
+}
+
+void AllocationSequence::CreateRelayPorts() {
+ if (flags_ & PORTALLOCATOR_DISABLE_RELAY) {
+ LOG(LS_VERBOSE) << "AllocationSequence: Relay ports disabled, skipping.";
+ return;
+ }
+
+ // If BasicPortAllocatorSession::OnAllocate left relay ports enabled then we
+ // ought to have a relay list for them here.
+ ASSERT(config_ && !config_->relays.empty());
+ if (!(config_ && !config_->relays.empty())) {
+ LOG(LS_WARNING)
+ << "AllocationSequence: No relay server configured, skipping.";
+ return;
+ }
+
+ PortConfiguration::RelayList::const_iterator relay;
+ for (relay = config_->relays.begin();
+ relay != config_->relays.end(); ++relay) {
+ RelayPort* port = RelayPort::Create(session_->network_thread(),
+ session_->allocator()->socket_factory(),
+ network_, ip_,
+ session_->allocator()->min_port(),
+ session_->allocator()->max_port(),
+ config_->username, config_->password,
+ config_->magic_cookie);
+ if (port) {
+ // Note: We must add the allocated port before we add addresses because
+ // the latter will create candidates that need name and preference
+ // settings. However, we also can't prepare the address (normally
+ // done by AddAllocatedPort) until we have these addresses. So we
+ // wait to do that until below.
+ session_->AddAllocatedPort(port, this, PREF_RELAY + relay->pref_modifier,
+ false);
+
+ // Add the addresses of this protocol.
+ PortConfiguration::PortList::const_iterator relay_port;
+ for (relay_port = relay->ports.begin();
+ relay_port != relay->ports.end();
+ ++relay_port) {
+ port->AddServerAddress(*relay_port);
+ port->AddExternalAddress(*relay_port);
+ }
+
+ // Start fetching an address for this port.
+ port->PrepareAddress();
+ }
+ }
+}
+
+// PortConfiguration
+PortConfiguration::PortConfiguration(const talk_base::SocketAddress& sa,
+ const std::string& un,
+ const std::string& pw,
+ const std::string& mc)
+ : stun_address(sa), username(un), password(pw), magic_cookie(mc) {
+}
+
+void PortConfiguration::AddRelay(const PortList& ports, float pref_modifier) {
+ RelayServer relay;
+ relay.ports = ports;
+ relay.pref_modifier = pref_modifier;
+ relays.push_back(relay);
+}
+
+bool PortConfiguration::ResolveStunAddress() {
+ int err = 0;
+ if (!stun_address.ResolveIP(true, &err)) {
+ LOG(LS_ERROR) << "Unable to resolve STUN host "
+ << stun_address.hostname() << ". Error " << err;
+ return false;
+ }
+ return true;
+}
+
+bool PortConfiguration::SupportsProtocol(
+ const PortConfiguration::RelayServer& relay, ProtocolType type) {
+ PortConfiguration::PortList::const_iterator relay_port;
+ for (relay_port = relay.ports.begin();
+ relay_port != relay.ports.end();
+ ++relay_port) {
+ if (relay_port->proto == type)
+ return true;
+ }
+ return false;
+}
+
+} // namespace cricket
diff --git a/talk/p2p/client/basicportallocator.h b/talk/p2p/client/basicportallocator.h
new file mode 100644
index 0000000..b2c1b8c
--- /dev/null
+++ b/talk/p2p/client/basicportallocator.h
@@ -0,0 +1,204 @@
+/*
+ * 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_P2P_CLIENT_BASICPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/messagequeue.h"
+#include "talk/base/network.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/portallocator.h"
+
+namespace cricket {
+
+class BasicPortAllocator : public PortAllocator {
+ public:
+ BasicPortAllocator(talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory);
+ BasicPortAllocator(talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const talk_base::SocketAddress& stun_server,
+ const talk_base::SocketAddress& relay_server_udp,
+ const talk_base::SocketAddress& relay_server_tcp,
+ const talk_base::SocketAddress& relay_server_ssl);
+ virtual ~BasicPortAllocator();
+
+ talk_base::NetworkManager* network_manager() { return network_manager_; }
+
+ talk_base::PacketSocketFactory* socket_factory() { return socket_factory_; }
+
+ const talk_base::SocketAddress& stun_address() const {
+ return stun_address_;
+ }
+ const talk_base::SocketAddress& relay_address_udp() const {
+ return relay_address_udp_;
+ }
+ const talk_base::SocketAddress& relay_address_tcp() const {
+ return relay_address_tcp_;
+ }
+ const talk_base::SocketAddress& relay_address_ssl() const {
+ return relay_address_ssl_;
+ }
+
+ // Returns the best (highest preference) phase that has produced a port that
+ // produced a writable connection. If no writable connections have been
+ // produced, this returns -1.
+ int best_writable_phase() const;
+
+ virtual PortAllocatorSession* CreateSession(const std::string& name,
+ const std::string& session_type);
+
+ // Called whenever a connection becomes writable with the argument being the
+ // phase that the corresponding port was created in.
+ void AddWritablePhase(int phase);
+
+ bool allow_tcp_listen() const {
+ return allow_tcp_listen_;
+ }
+ void set_allow_tcp_listen(bool allow_tcp_listen) {
+ allow_tcp_listen_ = allow_tcp_listen;
+ }
+
+ private:
+ talk_base::NetworkManager* network_manager_;
+ talk_base::PacketSocketFactory* socket_factory_;
+ const talk_base::SocketAddress stun_address_;
+ const talk_base::SocketAddress relay_address_udp_;
+ const talk_base::SocketAddress relay_address_tcp_;
+ const talk_base::SocketAddress relay_address_ssl_;
+ int best_writable_phase_;
+ bool allow_tcp_listen_;
+};
+
+struct PortConfiguration;
+class AllocationSequence;
+
+class BasicPortAllocatorSession : public PortAllocatorSession,
+ public talk_base::MessageHandler {
+ public:
+ BasicPortAllocatorSession(BasicPortAllocator* allocator,
+ const std::string& name,
+ const std::string& session_type);
+ ~BasicPortAllocatorSession();
+
+ virtual BasicPortAllocator* allocator() { return allocator_; }
+ const std::string& name() const { return name_; }
+ const std::string& session_type() const { return session_type_; }
+ talk_base::Thread* network_thread() { return network_thread_; }
+
+ virtual void GetInitialPorts();
+ virtual void StartGetAllPorts();
+ virtual void StopGetAllPorts();
+ virtual bool IsGettingAllPorts() { return running_; }
+
+ protected:
+ // Starts the process of getting the port configurations.
+ virtual void GetPortConfigurations();
+
+ // Adds a port configuration that is now ready. Once we have one for each
+ // network (or a timeout occurs), we will start allocating ports.
+ void ConfigReady(PortConfiguration* config);
+
+ // MessageHandler. Can be overriden if message IDs do not conflict.
+ virtual void OnMessage(talk_base::Message *message);
+
+ private:
+ void OnConfigReady(PortConfiguration* config);
+ void OnConfigTimeout();
+ void AllocatePorts();
+ void OnAllocate();
+ void DisableEquivalentPhases(talk_base::Network* network,
+ PortConfiguration* config, uint32* flags);
+ void AddAllocatedPort(Port* port, AllocationSequence* seq, float pref,
+ bool prepare_address = true);
+ void OnAddressReady(Port* port);
+ void OnProtocolEnabled(AllocationSequence* seq, ProtocolType proto);
+ void OnPortDestroyed(Port* port);
+ void OnConnectionCreated(Port* port, Connection* conn);
+ void OnConnectionStateChange(Connection* conn);
+ void OnShake();
+
+ BasicPortAllocator* allocator_;
+ std::string name_;
+ std::string session_type_;
+ talk_base::Thread* network_thread_;
+ bool configuration_done_;
+ bool allocation_started_;
+ bool running_; // set when StartGetAllPorts is called
+ std::vector<PortConfiguration*> configs_;
+ std::vector<AllocationSequence*> sequences_;
+
+ struct PortData {
+ Port* port;
+ AllocationSequence* sequence;
+ bool ready;
+
+ bool operator==(Port* rhs) const { return (port == rhs); }
+ };
+ std::vector<PortData> ports_;
+
+ friend class AllocationSequence;
+};
+
+// Records configuration information useful in creating ports.
+struct PortConfiguration : public talk_base::MessageData {
+ talk_base::SocketAddress stun_address;
+ std::string username;
+ std::string password;
+ std::string magic_cookie;
+
+ typedef std::vector<ProtocolAddress> PortList;
+ struct RelayServer {
+ PortList ports;
+ float pref_modifier; // added to the protocol modifier to get the
+ // preference for this particular server
+ };
+
+ typedef std::vector<RelayServer> RelayList;
+ RelayList relays;
+
+ PortConfiguration(const talk_base::SocketAddress& stun_address,
+ const std::string& username,
+ const std::string& password,
+ const std::string& magic_cookie);
+
+ // Adds another relay server, with the given ports and modifier, to the list.
+ void AddRelay(const PortList& ports, float pref_modifier);
+
+ bool ResolveStunAddress();
+
+ // Determines whether the given relay server supports the given protocol.
+ static bool SupportsProtocol(const PortConfiguration::RelayServer& relay,
+ ProtocolType type);
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_CLIENT_BASICPORTALLOCATOR_H_
diff --git a/talk/p2p/client/httpportallocator.cc b/talk/p2p/client/httpportallocator.cc
new file mode 100644
index 0000000..4ef0825
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.cc
@@ -0,0 +1,245 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/p2p/client/httpportallocator.h"
+
+#include <map>
+
+#include "talk/base/asynchttprequest.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/nethelpers.h"
+#include "talk/base/signalthread.h"
+
+namespace {
+
+const uint32 MSG_TIMEOUT = 100; // must not conflict
+ // with BasicPortAllocator.cpp
+
+// Helper routine to remove whitespace from the ends of a string.
+void Trim(std::string& str) {
+ size_t first = str.find_first_not_of(" \t\r\n");
+ if (first == std::string::npos) {
+ str.clear();
+ return;
+ }
+
+ ASSERT(str.find_last_not_of(" \t\r\n") != std::string::npos);
+}
+
+// Parses the lines in the result of the HTTP request that are of the form
+// 'a=b' and returns them in a map.
+typedef std::map<std::string, std::string> StringMap;
+void ParseMap(const std::string& string, StringMap& map) {
+ size_t start_of_line = 0;
+ size_t end_of_line = 0;
+
+ for (;;) { // for each line
+ start_of_line = string.find_first_not_of("\r\n", end_of_line);
+ if (start_of_line == std::string::npos)
+ break;
+
+ end_of_line = string.find_first_of("\r\n", start_of_line);
+ if (end_of_line == std::string::npos) {
+ end_of_line = string.length();
+ }
+
+ size_t equals = string.find('=', start_of_line);
+ if ((equals >= end_of_line) || (equals == std::string::npos))
+ continue;
+
+ std::string key(string, start_of_line, equals - start_of_line);
+ std::string value(string, equals + 1, end_of_line - equals - 1);
+
+ Trim(key);
+ Trim(value);
+
+ if ((key.size() > 0) && (value.size() > 0))
+ map[key] = value;
+ }
+}
+
+} // namespace
+
+namespace cricket {
+
+// HttpPortAllocator
+
+const int HttpPortAllocator::kHostPort = 80;
+const int HttpPortAllocator::kNumRetries = 5;
+
+const std::string HttpPortAllocator::kCreateSessionURL = "/create_session";
+
+HttpPortAllocator::HttpPortAllocator(
+ talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const std::string &user_agent)
+ : BasicPortAllocator(network_manager, socket_factory), agent_(user_agent) {
+ relay_hosts_.push_back("relay.google.com");
+ stun_hosts_.push_back(
+ talk_base::SocketAddress("stun.l.google.com", 19302));
+}
+
+HttpPortAllocator::~HttpPortAllocator() {
+}
+
+PortAllocatorSession *HttpPortAllocator::CreateSession(
+ const std::string& name, const std::string& session_type) {
+ return new HttpPortAllocatorSession(this, name, session_type, stun_hosts_,
+ relay_hosts_, relay_token_, agent_);
+}
+
+// HttpPortAllocatorSession
+
+HttpPortAllocatorSession::HttpPortAllocatorSession(
+ HttpPortAllocator* allocator, const std::string &name,
+ const std::string& session_type,
+ const std::vector<talk_base::SocketAddress>& stun_hosts,
+ const std::vector<std::string>& relay_hosts,
+ const std::string& relay_token,
+ const std::string& user_agent)
+ : BasicPortAllocatorSession(allocator, name, session_type),
+ relay_hosts_(relay_hosts), stun_hosts_(stun_hosts),
+ relay_token_(relay_token), agent_(user_agent), attempts_(0) {
+}
+
+void HttpPortAllocatorSession::GetPortConfigurations() {
+ // Creating relay sessions can take time and is done asynchronously.
+ // Creating stun sessions could also take time and could be done aysnc also,
+ // but for now is done here and added to the initial config. Note any later
+ // configs will have unresolved stun ips and will be discarded by the
+ // AllocationSequence.
+ PortConfiguration* config = new PortConfiguration(stun_hosts_[0], "", "", "");
+ ConfigReady(config);
+ TryCreateRelaySession();
+}
+
+void HttpPortAllocatorSession::TryCreateRelaySession() {
+ if (attempts_ == HttpPortAllocator::kNumRetries) {
+ LOG(LS_ERROR) << "HttpPortAllocator: maximum number of requests reached; "
+ << "giving up on relay.";
+ return;
+ }
+
+ if (relay_hosts_.size() == 0) {
+ LOG(LS_ERROR) << "HttpPortAllocator: no relay hosts configured.";
+ return;
+ }
+
+ // Choose the next host to try.
+ std::string host = relay_hosts_[attempts_ % relay_hosts_.size()];
+ attempts_++;
+ LOG(LS_INFO) << "HTTPPortAllocator: sending to relay host " << host;
+ if (relay_token_.empty()) {
+ LOG(LS_WARNING) << "No relay auth token found.";
+ }
+
+ SendSessionRequest(host, HttpPortAllocator::kHostPort);
+}
+
+void HttpPortAllocatorSession::SendSessionRequest(const std::string& host,
+ int port) {
+ // Initiate an HTTP request to create a session through the chosen host.
+ talk_base::AsyncHttpRequest* request =
+ new talk_base::AsyncHttpRequest(agent_);
+ request->SignalWorkDone.connect(this,
+ &HttpPortAllocatorSession::OnRequestDone);
+
+ request->set_proxy(allocator()->proxy());
+ request->response().document.reset(new talk_base::MemoryStream);
+ request->request().verb = talk_base::HV_GET;
+ request->request().path = HttpPortAllocator::kCreateSessionURL;
+ request->request().addHeader("X-Talk-Google-Relay-Auth", relay_token_, true);
+ request->request().addHeader("X-Google-Relay-Auth", relay_token_, true);
+ request->request().addHeader("X-Session-Type", session_type(), true);
+ request->request().addHeader("X-Stream-Type", name(), true);
+ request->set_host(host);
+ request->set_port(port);
+ request->Start();
+ request->Release();
+}
+
+void HttpPortAllocatorSession::OnRequestDone(talk_base::SignalThread* data) {
+ talk_base::AsyncHttpRequest* request =
+ static_cast<talk_base::AsyncHttpRequest*>(data);
+ if (request->response().scode != 200) {
+ LOG(LS_WARNING) << "HTTPPortAllocator: request "
+ << " received error " << request->response().scode;
+ TryCreateRelaySession();
+ return;
+ }
+ LOG(LS_INFO) << "HTTPPortAllocator: request succeeded";
+
+ talk_base::MemoryStream* stream =
+ static_cast<talk_base::MemoryStream*>(request->response().document.get());
+ stream->Rewind();
+ size_t length;
+ stream->GetSize(&length);
+ std::string resp = std::string(stream->GetBuffer(), length);
+ ReceiveSessionResponse(resp);
+}
+
+void HttpPortAllocatorSession::ReceiveSessionResponse(
+ const std::string& response) {
+
+ StringMap map;
+ ParseMap(response, map);
+
+ std::string username = map["username"];
+ std::string password = map["password"];
+ std::string magic_cookie = map["magic_cookie"];
+
+ std::string relay_ip = map["relay.ip"];
+ std::string relay_udp_port = map["relay.udp_port"];
+ std::string relay_tcp_port = map["relay.tcp_port"];
+ std::string relay_ssltcp_port = map["relay.ssltcp_port"];
+
+ PortConfiguration* config = new PortConfiguration(stun_hosts_[0],
+ username,
+ password,
+ magic_cookie);
+
+ PortConfiguration::PortList ports;
+ if (!relay_udp_port.empty()) {
+ talk_base::SocketAddress address(relay_ip, atoi(relay_udp_port.c_str()));
+ ports.push_back(ProtocolAddress(address, PROTO_UDP));
+ }
+ if (!relay_tcp_port.empty()) {
+ talk_base::SocketAddress address(relay_ip, atoi(relay_tcp_port.c_str()));
+ ports.push_back(ProtocolAddress(address, PROTO_TCP));
+ }
+ if (!relay_ssltcp_port.empty()) {
+ talk_base::SocketAddress address(relay_ip, atoi(relay_ssltcp_port.c_str()));
+ ports.push_back(ProtocolAddress(address, PROTO_SSLTCP));
+ }
+ config->AddRelay(ports, 0.0f);
+ ConfigReady(config);
+}
+
+} // namespace cricket
diff --git a/talk/p2p/client/httpportallocator.h b/talk/p2p/client/httpportallocator.h
new file mode 100644
index 0000000..478f278
--- /dev/null
+++ b/talk/p2p/client/httpportallocator.h
@@ -0,0 +1,135 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+#define TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
+
+#include <string>
+#include <vector>
+#include "talk/p2p/client/basicportallocator.h"
+
+namespace talk_base {
+class SignalThread;
+}
+
+namespace cricket {
+
+class HttpPortAllocator : public BasicPortAllocator {
+ public:
+ // Records the port on the hosts that will receive HTTP requests.
+ static const int kHostPort;
+
+ // The number of HTTP requests we should attempt before giving up.
+ static const int kNumRetries;
+
+ // Records the URL that we will GET in order to create a session.
+ static const std::string kCreateSessionURL;
+
+ HttpPortAllocator(talk_base::NetworkManager* network_manager,
+ talk_base::PacketSocketFactory* socket_factory,
+ const std::string& user_agent);
+ virtual ~HttpPortAllocator();
+
+ virtual PortAllocatorSession* CreateSession(const std::string& name,
+ const std::string& session_type);
+ void SetStunHosts(const std::vector<talk_base::SocketAddress>& hosts) {
+ if (!hosts.empty()) {
+ stun_hosts_ = hosts;
+ }
+ }
+ void SetRelayHosts(const std::vector<std::string>& hosts) {
+ if (!hosts.empty()) {
+ relay_hosts_ = hosts;
+ }
+ }
+ void SetRelayToken(const std::string& relay) { relay_token_ = relay; }
+
+ const std::vector<talk_base::SocketAddress>& stun_hosts() const {
+ return stun_hosts_;
+ }
+
+ const std::vector<std::string>& relay_hosts() const {
+ return relay_hosts_;
+ }
+
+ const std::string& relay_token() const {
+ return relay_token_;
+ }
+
+ const std::string& user_agent() const {
+ return agent_;
+ }
+
+ private:
+ std::vector<talk_base::SocketAddress> stun_hosts_;
+ std::vector<std::string> relay_hosts_;
+ std::string relay_token_;
+ std::string agent_;
+};
+
+class RequestData;
+
+class HttpPortAllocatorSession : public BasicPortAllocatorSession {
+ public:
+ HttpPortAllocatorSession(
+ HttpPortAllocator* allocator,
+ const std::string& name,
+ const std::string& session_type,
+ const std::vector<talk_base::SocketAddress>& stun_hosts,
+ const std::vector<std::string>& relay_hosts,
+ const std::string& relay,
+ const std::string& agent);
+ virtual ~HttpPortAllocatorSession() {}
+
+ const std::string& relay_token() const {
+ return relay_token_;
+ }
+ virtual void SendSessionRequest(const std::string& host, int port);
+ virtual void ReceiveSessionResponse(const std::string& response);
+
+ protected:
+ virtual void GetPortConfigurations();
+ void TryCreateRelaySession();
+
+ private:
+ virtual HttpPortAllocator* allocator() {
+ return static_cast<HttpPortAllocator*>(
+ BasicPortAllocatorSession::allocator());
+ }
+
+ void OnRequestDone(talk_base::SignalThread* request);
+
+ std::vector<std::string> relay_hosts_;
+ std::vector<talk_base::SocketAddress> stun_hosts_;
+ std::string relay_token_;
+ std::string agent_;
+ int attempts_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_CLIENT_HTTPPORTALLOCATOR_H_
diff --git a/talk/p2p/client/sessionmanagertask.h b/talk/p2p/client/sessionmanagertask.h
new file mode 100644
index 0000000..2026bf6
--- /dev/null
+++ b/talk/p2p/client/sessionmanagertask.h
@@ -0,0 +1,92 @@
+/*
+ * 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 _SESSIONMANAGERTASK_H_
+#define _SESSIONMANAGERTASK_H_
+
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/client/sessionsendtask.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+
+namespace cricket {
+
+// This class handles sending and receiving XMPP messages on behalf of the
+// SessionManager. The sending part is handed over to SessionSendTask.
+
+class SessionManagerTask : public buzz::XmppTask {
+ public:
+ SessionManagerTask(TaskParent *parent, SessionManager *session_manager)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE) {
+ session_manager_ = session_manager;
+ }
+
+ ~SessionManagerTask() {
+ }
+
+ // Turns on simple support for sending messages, using SessionSendTask.
+ void EnableOutgoingMessages() {
+ session_manager_->SignalOutgoingMessage.connect(
+ this, &SessionManagerTask::OnOutgoingMessage);
+ session_manager_->SignalRequestSignaling.connect(
+ session_manager_, &SessionManager::OnSignalingReady);
+ }
+
+ virtual int ProcessStart() {
+ const buzz::XmlElement *stanza = NextStanza();
+ if (stanza == NULL)
+ return STATE_BLOCKED;
+ session_manager_->OnIncomingMessage(stanza);
+ return STATE_START;
+ }
+
+ protected:
+ virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+ if (!session_manager_->IsSessionMessage(stanza))
+ return false;
+ // Responses are handled by the SessionSendTask that sent the request.
+ //if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET)
+ // return false;
+ QueueStanza(stanza);
+ return true;
+ }
+
+ private:
+ SessionManager* session_manager_;
+
+ void OnOutgoingMessage(SessionManager* manager,
+ const buzz::XmlElement* stanza) {
+ cricket::SessionSendTask* sender =
+ new cricket::SessionSendTask(GetParent(), session_manager_);
+ sender->Send(stanza);
+ sender->Start();
+ }
+};
+
+} // namespace cricket
+
+#endif // _SESSIONMANAGERTASK_H_
diff --git a/talk/p2p/client/sessionsendtask.h b/talk/p2p/client/sessionsendtask.h
new file mode 100644
index 0000000..7bdc067
--- /dev/null
+++ b/talk/p2p/client/sessionsendtask.h
@@ -0,0 +1,137 @@
+/*
+ * 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 _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
+#define _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
+
+#include "talk/base/common.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmpptask.h"
+#include "talk/p2p/base/sessionmanager.h"
+
+namespace cricket {
+
+// The job of this task is to send an IQ stanza out (after stamping it with
+// an ID attribute) and then wait for a response. If not response happens
+// within 5 seconds, it will signal failure on a SessionManager. If an error
+// happens it will also signal failure. If, however, the send succeeds this
+// task will quietly go away.
+
+class SessionSendTask : public buzz::XmppTask {
+public:
+ SessionSendTask(TaskParent *parent, SessionManager *session_manager)
+ : buzz::XmppTask(parent, buzz::XmppEngine::HL_SINGLE),
+ session_manager_(session_manager) {
+ set_timeout_seconds(15);
+ }
+
+ virtual ~SessionSendTask() {
+ SignalDone(this);
+ }
+
+ void Send(const buzz::XmlElement* stanza) {
+ ASSERT(stanza_.get() == NULL);
+
+ // This should be an IQ of type set, result, or error. In the first case,
+ // we supply an ID. In the others, it should be present.
+ ASSERT(stanza->Name() == buzz::QN_IQ);
+ ASSERT(stanza->HasAttr(buzz::QN_TYPE));
+ if (stanza->Attr(buzz::QN_TYPE) == "set") {
+ ASSERT(!stanza->HasAttr(buzz::QN_ID));
+ } else {
+ ASSERT((stanza->Attr(buzz::QN_TYPE) == "result") ||
+ (stanza->Attr(buzz::QN_TYPE) == "error"));
+ ASSERT(stanza->HasAttr(buzz::QN_ID));
+ }
+
+ stanza_.reset(new buzz::XmlElement(*stanza));
+ if (stanza_->HasAttr(buzz::QN_ID)) {
+ set_task_id(stanza_->Attr(buzz::QN_ID));
+ } else {
+ stanza_->SetAttr(buzz::QN_ID, task_id());
+ }
+ }
+
+ sigslot::signal1<SessionSendTask *> SignalDone;
+
+protected:
+ virtual int OnTimeout() {
+ session_manager_->OnFailedSend(stanza_.get(), NULL);
+
+ return XmppTask::OnTimeout();
+ }
+
+ virtual int ProcessStart() {
+ SendStanza(stanza_.get());
+ if (stanza_->Attr(buzz::QN_TYPE) == buzz::STR_SET) {
+ return STATE_RESPONSE;
+ } else {
+ return STATE_DONE;
+ }
+ }
+
+ virtual int ProcessResponse() {
+ if (GetClient()->GetState() != buzz::XmppEngine::STATE_OPEN) {
+ return STATE_DONE;
+ }
+
+ const buzz::XmlElement* next = NextStanza();
+ if (next == NULL)
+ return STATE_BLOCKED;
+
+ if (next->Attr(buzz::QN_TYPE) == buzz::STR_RESULT) {
+ session_manager_->OnIncomingResponse(stanza_.get(), next);
+ } else {
+ session_manager_->OnFailedSend(stanza_.get(), next);
+ }
+
+ return STATE_DONE;
+ }
+
+ virtual bool HandleStanza(const buzz::XmlElement *stanza) {
+ if (!MatchResponseIq(stanza,
+ buzz::Jid(stanza_->Attr(buzz::QN_TO)), task_id()))
+ return false;
+ if (stanza->Attr(buzz::QN_TYPE) == buzz::STR_RESULT ||
+ stanza->Attr(buzz::QN_TYPE) == buzz::STR_ERROR) {
+ QueueStanza(stanza);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ SessionManager *session_manager_;
+ talk_base::scoped_ptr<buzz::XmlElement> stanza_;
+ bool timed_out_;
+};
+
+}
+
+#endif // _CRICKET_P2P_CLIENT_SESSIONSENDTASK_H_
diff --git a/talk/p2p/client/socketmonitor.cc b/talk/p2p/client/socketmonitor.cc
new file mode 100644
index 0000000..bf32d84
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.cc
@@ -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.
+ */
+
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/base/common.h"
+
+namespace cricket {
+
+const uint32 MSG_MONITOR_POLL = 1;
+const uint32 MSG_MONITOR_START = 2;
+const uint32 MSG_MONITOR_STOP = 3;
+const uint32 MSG_MONITOR_SIGNAL = 4;
+
+SocketMonitor::SocketMonitor(TransportChannel* channel,
+ talk_base::Thread* worker_thread,
+ talk_base::Thread* monitor_thread) {
+ channel_ = channel;
+ channel_thread_ = worker_thread;
+ monitoring_thread_ = monitor_thread;
+ monitoring_ = false;
+}
+
+SocketMonitor::~SocketMonitor() {
+ channel_thread_->Clear(this);
+ monitoring_thread_->Clear(this);
+}
+
+void SocketMonitor::Start(int milliseconds) {
+ rate_ = milliseconds;
+ if (rate_ < 250)
+ rate_ = 250;
+ channel_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void SocketMonitor::Stop() {
+ channel_thread_->Post(this, MSG_MONITOR_STOP);
+}
+
+void SocketMonitor::OnMessage(talk_base::Message *message) {
+ talk_base::CritScope cs(&crit_);
+
+ switch (message->message_id) {
+ case MSG_MONITOR_START:
+ ASSERT(talk_base::Thread::Current() == channel_thread_);
+ if (!monitoring_) {
+ monitoring_ = true;
+ if (GetP2PChannel() != NULL) {
+ GetP2PChannel()->SignalConnectionMonitor.connect(
+ this, &SocketMonitor::OnConnectionMonitor);
+ }
+ PollSocket(true);
+ }
+ break;
+
+ case MSG_MONITOR_STOP:
+ ASSERT(talk_base::Thread::Current() == channel_thread_);
+ if (monitoring_) {
+ monitoring_ = false;
+ if (GetP2PChannel() != NULL)
+ GetP2PChannel()->SignalConnectionMonitor.disconnect(this);
+ channel_thread_->Clear(this);
+ }
+ break;
+
+ case MSG_MONITOR_POLL:
+ ASSERT(talk_base::Thread::Current() == channel_thread_);
+ PollSocket(true);
+ break;
+
+ case MSG_MONITOR_SIGNAL:
+ {
+ ASSERT(talk_base::Thread::Current() == monitoring_thread_);
+ std::vector<ConnectionInfo> infos = connection_infos_;
+ crit_.Leave();
+ SignalUpdate(this, infos);
+ crit_.Enter();
+ }
+ break;
+ }
+}
+
+void SocketMonitor::OnConnectionMonitor(P2PTransportChannel* channel) {
+ talk_base::CritScope cs(&crit_);
+ if (monitoring_)
+ PollSocket(false);
+}
+
+void SocketMonitor::PollSocket(bool poll) {
+ ASSERT(talk_base::Thread::Current() == channel_thread_);
+ talk_base::CritScope cs(&crit_);
+
+ // Gather connection infos
+ P2PTransportChannel* p2p_channel = GetP2PChannel();
+ if (p2p_channel != NULL) {
+ connection_infos_.clear();
+ const std::vector<Connection *> &connections = p2p_channel->connections();
+ std::vector<Connection *>::const_iterator it;
+ for (it = connections.begin(); it != connections.end(); it++) {
+ Connection *connection = *it;
+ ConnectionInfo info;
+ info.best_connection = p2p_channel->best_connection() == connection;
+ info.readable =
+ (connection->read_state() == Connection::STATE_READABLE);
+ info.writable =
+ (connection->write_state() == Connection::STATE_WRITABLE);
+ info.timeout =
+ (connection->write_state() == Connection::STATE_WRITE_TIMEOUT);
+ info.new_connection = !connection->reported();
+ connection->set_reported(true);
+ info.rtt = connection->rtt();
+ info.sent_total_bytes = connection->sent_total_bytes();
+ info.sent_bytes_second = connection->sent_bytes_second();
+ info.recv_total_bytes = connection->recv_total_bytes();
+ info.recv_bytes_second = connection->recv_bytes_second();
+ info.local_candidate = connection->local_candidate();
+ info.remote_candidate = connection->remote_candidate();
+ info.est_quality = connection->port()->network()->quality();
+ info.key = connection;
+ connection_infos_.push_back(info);
+ }
+ }
+
+ // Signal the monitoring thread, start another poll timer
+
+ monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+ if (poll)
+ channel_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}
diff --git a/talk/p2p/client/socketmonitor.h b/talk/p2p/client/socketmonitor.h
new file mode 100644
index 0000000..cf29d9d
--- /dev/null
+++ b/talk/p2p/client/socketmonitor.h
@@ -0,0 +1,91 @@
+/*
+ * 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_P2P_CLIENT_SOCKETMONITOR_H_
+#define TALK_P2P_CLIENT_SOCKETMONITOR_H_
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/p2ptransportchannel.h"
+#include "talk/p2p/base/port.h"
+
+namespace cricket {
+
+struct ConnectionInfo {
+ bool best_connection;
+ bool writable;
+ bool readable;
+ bool timeout;
+ bool new_connection;
+ size_t rtt;
+ size_t sent_total_bytes;
+ size_t sent_bytes_second;
+ size_t recv_total_bytes;
+ size_t recv_bytes_second;
+ Candidate local_candidate;
+ Candidate remote_candidate;
+ double est_quality;
+ void *key;
+};
+
+class SocketMonitor : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ SocketMonitor(TransportChannel* channel,
+ talk_base::Thread* worker_thread,
+ talk_base::Thread* monitor_thread);
+ ~SocketMonitor();
+
+ void Start(int cms);
+ void Stop();
+
+ talk_base::Thread *monitor_thread() { return monitoring_thread_; }
+
+ sigslot::signal2<SocketMonitor *,
+ const std::vector<ConnectionInfo> &> SignalUpdate;
+
+ protected:
+ P2PTransportChannel* GetP2PChannel() { return channel_->GetP2PChannel(); }
+ void OnMessage(talk_base::Message *message);
+ void OnConnectionMonitor(P2PTransportChannel* channel);
+ void PollSocket(bool poll);
+
+ std::vector<ConnectionInfo> connection_infos_;
+ TransportChannel* channel_;
+ talk_base::Thread* channel_thread_;
+ talk_base::Thread* monitoring_thread_;
+ talk_base::CriticalSection crit_;
+ uint32 rate_;
+ bool monitoring_;
+};
+
+} // namespace cricket
+
+#endif // TALK_P2P_CLIENT_SOCKETMONITOR_H_
diff --git a/talk/session/phone/audiomonitor.cc b/talk/session/phone/audiomonitor.cc
new file mode 100644
index 0000000..d980ded
--- /dev/null
+++ b/talk/session/phone/audiomonitor.cc
@@ -0,0 +1,121 @@
+/*
+ * 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 "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/voicechannel.h"
+#include <cassert>
+
+namespace cricket {
+
+const uint32 MSG_MONITOR_POLL = 1;
+const uint32 MSG_MONITOR_START = 2;
+const uint32 MSG_MONITOR_STOP = 3;
+const uint32 MSG_MONITOR_SIGNAL = 4;
+
+AudioMonitor::AudioMonitor(VoiceChannel *voice_channel,
+ talk_base::Thread *monitor_thread) {
+ voice_channel_ = voice_channel;
+ monitoring_thread_ = monitor_thread;
+ monitoring_ = false;
+}
+
+AudioMonitor::~AudioMonitor() {
+ voice_channel_->worker_thread()->Clear(this);
+ monitoring_thread_->Clear(this);
+}
+
+void AudioMonitor::Start(int milliseconds) {
+ rate_ = milliseconds;
+ if (rate_ < 100)
+ rate_ = 100;
+ voice_channel_->worker_thread()->Post(this, MSG_MONITOR_START);
+}
+
+void AudioMonitor::Stop() {
+ voice_channel_->worker_thread()->Post(this, MSG_MONITOR_STOP);
+}
+
+void AudioMonitor::OnMessage(talk_base::Message *message) {
+ talk_base::CritScope cs(&crit_);
+
+ switch (message->message_id) {
+ case MSG_MONITOR_START:
+ assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+ if (!monitoring_) {
+ monitoring_ = true;
+ PollVoiceChannel();
+ }
+ break;
+
+ case MSG_MONITOR_STOP:
+ assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+ if (monitoring_) {
+ monitoring_ = false;
+ voice_channel_->worker_thread()->Clear(this);
+ }
+ break;
+
+ case MSG_MONITOR_POLL:
+ assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+ PollVoiceChannel();
+ break;
+
+ case MSG_MONITOR_SIGNAL:
+ {
+ assert(talk_base::Thread::Current() == monitoring_thread_);
+ AudioInfo info = audio_info_;
+ crit_.Leave();
+ SignalUpdate(this, info);
+ crit_.Enter();
+ }
+ break;
+ }
+}
+
+void AudioMonitor::PollVoiceChannel() {
+ talk_base::CritScope cs(&crit_);
+ assert(talk_base::Thread::Current() == voice_channel_->worker_thread());
+
+ // Gather connection infos
+ audio_info_.input_level = voice_channel_->GetInputLevel_w();
+ audio_info_.output_level = voice_channel_->GetOutputLevel_w();
+ voice_channel_->GetActiveStreams_w(&audio_info_.active_streams);
+
+ // Signal the monitoring thread, start another poll timer
+ monitoring_thread_->Post(this, MSG_MONITOR_SIGNAL);
+ voice_channel_->worker_thread()->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+VoiceChannel *AudioMonitor::voice_channel() {
+ return voice_channel_;
+}
+
+talk_base::Thread *AudioMonitor::monitor_thread() {
+ return monitoring_thread_;
+}
+
+}
diff --git a/talk/session/phone/audiomonitor.h b/talk/session/phone/audiomonitor.h
new file mode 100644
index 0000000..e94ea68
--- /dev/null
+++ b/talk/session/phone/audiomonitor.h
@@ -0,0 +1,75 @@
+/*
+ * 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 _CRICKET_PHONE_AUDIOMONITOR_H_
+#define _CRICKET_PHONE_AUDIOMONITOR_H_
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/base/port.h"
+#include <vector>
+
+namespace cricket {
+
+class VoiceChannel;
+
+struct AudioInfo {
+ int input_level;
+ int output_level;
+ typedef std::vector<std::pair<uint32, int> > StreamList;
+ StreamList active_streams; // ssrcs contributing to output_level
+};
+
+class AudioMonitor : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+public:
+ AudioMonitor(VoiceChannel* voice_channel, talk_base::Thread *monitor_thread);
+ ~AudioMonitor();
+
+ void Start(int cms);
+ void Stop();
+
+ VoiceChannel* voice_channel();
+ talk_base::Thread *monitor_thread();
+
+ sigslot::signal2<AudioMonitor*, const AudioInfo&> SignalUpdate;
+
+protected:
+ void OnMessage(talk_base::Message *message);
+ void PollVoiceChannel();
+
+ AudioInfo audio_info_;
+ VoiceChannel* voice_channel_;
+ talk_base::Thread* monitoring_thread_;
+ talk_base::CriticalSection crit_;
+ uint32 rate_;
+ bool monitoring_;
+};
+
+}
+
+#endif // _CRICKET_PHONE_AUDIOMONITOR_H_
diff --git a/talk/session/phone/call.cc b/talk/session/phone/call.cc
new file mode 100644
index 0000000..fa4d33d
--- /dev/null
+++ b/talk/session/phone/call.cc
@@ -0,0 +1,499 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/thread.h"
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/mediasessionclient.h"
+
+namespace cricket {
+
+const uint32 MSG_CHECKAUTODESTROY = 1;
+const uint32 MSG_TERMINATECALL = 2;
+const uint32 MSG_PLAYDTMF = 3;
+
+namespace {
+const int kDTMFDelay = 300; // msec
+const size_t kMaxDTMFDigits = 30;
+const int kSendToVoicemailTimeout = 1000*20;
+const int kNoVoicemailTimeout = 1000*180;
+const int kMediaMonitorInterval = 1000*15;
+}
+
+Call::Call(MediaSessionClient* session_client)
+ : id_(talk_base::CreateRandomId()),
+ session_client_(session_client),
+ local_renderer_(NULL),
+ muted_(false),
+ send_to_voicemail_(true),
+ playing_dtmf_(false) {
+}
+
+Call::~Call() {
+ while (sessions_.begin() != sessions_.end()) {
+ Session *session = sessions_[0];
+ RemoveSession(session);
+ session_client_->session_manager()->DestroySession(session);
+ }
+ talk_base::Thread::Current()->Clear(this);
+}
+
+Session *Call::InitiateSession(const buzz::Jid &jid,
+ const CallOptions& options) {
+ const SessionDescription* offer = session_client_->CreateOffer(options);
+
+ Session *session = session_client_->CreateSession(this);
+ AddSession(session, offer);
+ session->Initiate(jid.Str(), offer);
+
+ // After this timeout, terminate the call because the callee isn't
+ // answering
+ session_client_->session_manager()->signaling_thread()->Clear(this,
+ MSG_TERMINATECALL);
+ session_client_->session_manager()->signaling_thread()->PostDelayed(
+ send_to_voicemail_ ? kSendToVoicemailTimeout : kNoVoicemailTimeout,
+ this, MSG_TERMINATECALL);
+ return session;
+}
+
+void Call::IncomingSession(
+ Session* session, const SessionDescription* offer) {
+ AddSession(session, offer);
+
+ // Missed the first state, the initiate, which is needed by
+ // call_client.
+ SignalSessionState(this, session, Session::STATE_RECEIVEDINITIATE);
+}
+
+void Call::AcceptSession(BaseSession* session,
+ const cricket::CallOptions& options) {
+ std::vector<Session *>::iterator it;
+ it = std::find(sessions_.begin(), sessions_.end(), session);
+ ASSERT(it != sessions_.end());
+ if (it != sessions_.end()) {
+ session->Accept(
+ session_client_->CreateAnswer(session->remote_description(), options));
+ }
+}
+
+void Call::RejectSession(BaseSession *session) {
+ std::vector<Session *>::iterator it;
+ it = std::find(sessions_.begin(), sessions_.end(), session);
+ ASSERT(it != sessions_.end());
+ // Assume polite decline.
+ if (it != sessions_.end())
+ session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void Call::TerminateSession(BaseSession *session) {
+ ASSERT(std::find(sessions_.begin(), sessions_.end(), session)
+ != sessions_.end());
+ std::vector<Session *>::iterator it;
+ it = std::find(sessions_.begin(), sessions_.end(), session);
+ // Assume polite terminations.
+ if (it != sessions_.end())
+ (*it)->Terminate();
+}
+
+void Call::Terminate() {
+ // Copy the list so that we can iterate over it in a stable way
+ std::vector<Session *> sessions = sessions_;
+
+ // There may be more than one session to terminate
+ std::vector<Session *>::iterator it;
+ for (it = sessions.begin(); it != sessions.end(); it++)
+ TerminateSession(*it);
+}
+
+void Call::SetLocalRenderer(VideoRenderer* renderer) {
+ local_renderer_ = renderer;
+ if (session_client_->GetFocus() == this) {
+ session_client_->channel_manager()->SetLocalRenderer(renderer);
+ }
+}
+
+void Call::SetVideoRenderer(BaseSession *session, uint32 ssrc,
+ VideoRenderer* renderer) {
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (video_channel) {
+ video_channel->SetRenderer(ssrc, renderer);
+ }
+}
+
+void Call::AddStream(BaseSession *session,
+ uint32 voice_ssrc, uint32 video_ssrc) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (voice_channel && voice_ssrc) {
+ voice_channel->AddStream(voice_ssrc);
+ }
+ if (video_channel && video_ssrc) {
+ video_channel->AddStream(video_ssrc, voice_ssrc);
+ }
+}
+
+void Call::RemoveStream(BaseSession *session,
+ uint32 voice_ssrc, uint32 video_ssrc) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (voice_channel && voice_ssrc) {
+ voice_channel->RemoveStream(voice_ssrc);
+ }
+ if (video_channel && video_ssrc) {
+ video_channel->RemoveStream(video_ssrc);
+ }
+}
+
+void Call::OnMessage(talk_base::Message *message) {
+ switch (message->message_id) {
+ case MSG_CHECKAUTODESTROY:
+ // If no more sessions for this call, delete it
+ if (sessions_.size() == 0)
+ session_client_->DestroyCall(this);
+ break;
+ case MSG_TERMINATECALL:
+ // Signal to the user that a timeout has happened and the call should
+ // be sent to voicemail.
+ if (send_to_voicemail_) {
+ SignalSetupToCallVoicemail();
+ }
+
+ // Callee didn't answer - terminate call
+ Terminate();
+ break;
+ case MSG_PLAYDTMF:
+ ContinuePlayDTMF();
+ }
+}
+
+const std::vector<Session *> &Call::sessions() {
+ return sessions_;
+}
+
+bool Call::AddSession(Session *session, const SessionDescription* offer) {
+ bool succeeded = true;
+ VoiceChannel *voice_channel = NULL;
+ VideoChannel *video_channel = NULL;
+
+ const ContentInfo* audio_offer = GetFirstAudioContent(offer);
+ const ContentInfo* video_offer = GetFirstVideoContent(offer);
+ video_ = (video_offer != NULL);
+
+ ASSERT(audio_offer != NULL);
+ // Create voice channel and start a media monitor
+ voice_channel = session_client_->channel_manager()->CreateVoiceChannel(
+ session, audio_offer->name, video_);
+ // voice_channel can be NULL in case of NullVoiceEngine.
+ if (voice_channel) {
+ voice_channel_map_[session->id()] = voice_channel;
+
+ voice_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
+ voice_channel->StartMediaMonitor(kMediaMonitorInterval);
+ } else {
+ succeeded = false;
+ }
+
+ // If desired, create video channel and start a media monitor
+ if (video_ && succeeded) {
+ video_channel = session_client_->channel_manager()->CreateVideoChannel(
+ session, video_offer->name, true, voice_channel);
+ // video_channel can be NULL in case of NullVideoEngine.
+ if (video_channel) {
+ video_channel_map_[session->id()] = video_channel;
+
+ video_channel->SignalMediaMonitor.connect(this, &Call::OnMediaMonitor);
+ video_channel->StartMediaMonitor(kMediaMonitorInterval);
+ } else {
+ succeeded = false;
+ }
+ }
+
+ if (succeeded) {
+ // Add session to list, create channels for this session
+ sessions_.push_back(session);
+ session->SignalState.connect(this, &Call::OnSessionState);
+ session->SignalError.connect(this, &Call::OnSessionError);
+ session->SignalReceivedTerminateReason
+ .connect(this, &Call::OnReceivedTerminateReason);
+
+ // If this call has the focus, enable this channel
+ if (session_client_->GetFocus() == this) {
+ voice_channel->Enable(true);
+ if (video_channel) {
+ video_channel->Enable(true);
+ }
+ }
+
+ // Signal client
+ SignalAddSession(this, session);
+ }
+
+ return succeeded;
+}
+
+void Call::RemoveSession(Session *session) {
+ // Remove session from list
+ std::vector<Session *>::iterator it_session;
+ it_session = std::find(sessions_.begin(), sessions_.end(), session);
+ if (it_session == sessions_.end())
+ return;
+ sessions_.erase(it_session);
+
+ // Destroy video channel
+ std::map<std::string, VideoChannel *>::iterator it_vchannel;
+ it_vchannel = video_channel_map_.find(session->id());
+ if (it_vchannel != video_channel_map_.end()) {
+ VideoChannel *video_channel = it_vchannel->second;
+ video_channel_map_.erase(it_vchannel);
+ session_client_->channel_manager()->DestroyVideoChannel(video_channel);
+ }
+
+ // Destroy voice channel
+ std::map<std::string, VoiceChannel *>::iterator it_channel;
+ it_channel = voice_channel_map_.find(session->id());
+ if (it_channel != voice_channel_map_.end()) {
+ VoiceChannel *voice_channel = it_channel->second;
+ voice_channel_map_.erase(it_channel);
+ session_client_->channel_manager()->DestroyVoiceChannel(voice_channel);
+ }
+
+ // Signal client
+ SignalRemoveSession(this, session);
+
+ // The call auto destroys when the last session is removed
+ talk_base::Thread::Current()->Post(this, MSG_CHECKAUTODESTROY);
+}
+
+VoiceChannel* Call::GetVoiceChannel(BaseSession* session) {
+ std::map<std::string, VoiceChannel *>::iterator it
+ = voice_channel_map_.find(session->id());
+ return (it != voice_channel_map_.end()) ? it->second : NULL;
+}
+
+VideoChannel* Call::GetVideoChannel(BaseSession* session) {
+ std::map<std::string, VideoChannel *>::iterator it
+ = video_channel_map_.find(session->id());
+ return (it != video_channel_map_.end()) ? it->second : NULL;
+}
+
+void Call::EnableChannels(bool enable) {
+ std::vector<Session *>::iterator it;
+ for (it = sessions_.begin(); it != sessions_.end(); it++) {
+ VoiceChannel *voice_channel = GetVoiceChannel(*it);
+ VideoChannel *video_channel = GetVideoChannel(*it);
+ if (voice_channel != NULL)
+ voice_channel->Enable(enable);
+ if (video_channel != NULL)
+ video_channel->Enable(enable);
+ }
+ session_client_->channel_manager()->SetLocalRenderer(
+ (enable) ? local_renderer_ : NULL);
+}
+
+void Call::Mute(bool mute) {
+ muted_ = mute;
+ std::vector<Session *>::iterator it;
+ for (it = sessions_.begin(); it != sessions_.end(); it++) {
+ VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
+ if (voice_channel != NULL)
+ voice_channel->Mute(mute);
+ }
+}
+
+void Call::PressDTMF(int event) {
+ // Queue up this digit
+ if (queued_dtmf_.size() < kMaxDTMFDigits) {
+ LOG(LS_INFO) << "Call::PressDTMF(" << event << ")";
+
+ queued_dtmf_.push_back(event);
+
+ if (!playing_dtmf_) {
+ ContinuePlayDTMF();
+ }
+ }
+}
+
+void Call::ContinuePlayDTMF() {
+ playing_dtmf_ = false;
+
+ // Check to see if we have a queued tone
+ if (queued_dtmf_.size() > 0) {
+ playing_dtmf_ = true;
+
+ int tone = queued_dtmf_.front();
+ queued_dtmf_.pop_front();
+
+ LOG(LS_INFO) << "Call::ContinuePlayDTMF(" << tone << ")";
+ std::vector<Session *>::iterator it;
+ for (it = sessions_.begin(); it != sessions_.end(); it++) {
+ VoiceChannel *voice_channel = voice_channel_map_[(*it)->id()];
+ if (voice_channel != NULL) {
+ voice_channel->PressDTMF(tone, true);
+ }
+ }
+
+ // Post a message to play the next tone or at least clear the playing_dtmf_
+ // bit.
+ talk_base::Thread::Current()->PostDelayed(kDTMFDelay, this, MSG_PLAYDTMF);
+ }
+}
+
+void Call::Join(Call *call, bool enable) {
+ while (call->sessions_.size() != 0) {
+ // Move session
+ Session *session = call->sessions_[0];
+ call->sessions_.erase(call->sessions_.begin());
+ sessions_.push_back(session);
+ session->SignalState.connect(this, &Call::OnSessionState);
+ session->SignalError.connect(this, &Call::OnSessionError);
+ session->SignalReceivedTerminateReason
+ .connect(this, &Call::OnReceivedTerminateReason);
+
+ // Move voice channel
+ std::map<std::string, VoiceChannel *>::iterator it_channel;
+ it_channel = call->voice_channel_map_.find(session->id());
+ if (it_channel != call->voice_channel_map_.end()) {
+ VoiceChannel *voice_channel = (*it_channel).second;
+ call->voice_channel_map_.erase(it_channel);
+ voice_channel_map_[session->id()] = voice_channel;
+ voice_channel->Enable(enable);
+ }
+
+ // Move video channel
+ std::map<std::string, VideoChannel *>::iterator it_vchannel;
+ it_vchannel = call->video_channel_map_.find(session->id());
+ if (it_vchannel != call->video_channel_map_.end()) {
+ VideoChannel *video_channel = (*it_vchannel).second;
+ call->video_channel_map_.erase(it_vchannel);
+ video_channel_map_[session->id()] = video_channel;
+ video_channel->Enable(enable);
+ }
+ }
+}
+
+void Call::StartConnectionMonitor(BaseSession *session, int cms) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ if (voice_channel) {
+ voice_channel->SignalConnectionMonitor.connect(this,
+ &Call::OnConnectionMonitor);
+ voice_channel->StartConnectionMonitor(cms);
+ }
+
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (video_channel) {
+ video_channel->SignalConnectionMonitor.connect(this,
+ &Call::OnConnectionMonitor);
+ video_channel->StartConnectionMonitor(cms);
+ }
+}
+
+void Call::StopConnectionMonitor(BaseSession *session) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ if (voice_channel) {
+ voice_channel->StopConnectionMonitor();
+ voice_channel->SignalConnectionMonitor.disconnect(this);
+ }
+
+ VideoChannel *video_channel = GetVideoChannel(session);
+ if (video_channel) {
+ video_channel->StopConnectionMonitor();
+ video_channel->SignalConnectionMonitor.disconnect(this);
+ }
+}
+
+void Call::StartAudioMonitor(BaseSession *session, int cms) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ if (voice_channel) {
+ voice_channel->SignalAudioMonitor.connect(this, &Call::OnAudioMonitor);
+ voice_channel->StartAudioMonitor(cms);
+ }
+}
+
+void Call::StopAudioMonitor(BaseSession *session) {
+ VoiceChannel *voice_channel = GetVoiceChannel(session);
+ if (voice_channel) {
+ voice_channel->StopAudioMonitor();
+ voice_channel->SignalAudioMonitor.disconnect(this);
+ }
+}
+
+void Call::OnConnectionMonitor(VoiceChannel *channel,
+ const std::vector<ConnectionInfo> &infos) {
+ SignalConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VoiceChannel *channel, const VoiceMediaInfo& info) {
+ SignalMediaMonitor(this, info);
+}
+
+void Call::OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info) {
+ SignalAudioMonitor(this, info);
+}
+
+void Call::OnConnectionMonitor(VideoChannel *channel,
+ const std::vector<ConnectionInfo> &infos) {
+ SignalVideoConnectionMonitor(this, infos);
+}
+
+void Call::OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info) {
+ SignalVideoMediaMonitor(this, info);
+}
+
+uint32 Call::id() {
+ return id_;
+}
+
+void Call::OnSessionState(BaseSession *session, BaseSession::State state) {
+ switch (state) {
+ case Session::STATE_RECEIVEDACCEPT:
+ case Session::STATE_RECEIVEDREJECT:
+ case Session::STATE_RECEIVEDTERMINATE:
+ session_client_->session_manager()->signaling_thread()->Clear(this,
+ MSG_TERMINATECALL);
+ break;
+ default:
+ break;
+ }
+ SignalSessionState(this, session, state);
+}
+
+void Call::OnSessionError(BaseSession *session, Session::Error error) {
+ session_client_->session_manager()->signaling_thread()->Clear(this,
+ MSG_TERMINATECALL);
+ SignalSessionError(this, session, error);
+}
+
+void Call::OnReceivedTerminateReason(Session *session,
+ const std::string &reason) {
+ session_client_->session_manager()->signaling_thread()->Clear(this,
+ MSG_TERMINATECALL);
+ SignalReceivedTerminateReason(this, session, reason);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/call.h b/talk/session/phone/call.h
new file mode 100644
index 0000000..8e9bcdc
--- /dev/null
+++ b/talk/session/phone/call.h
@@ -0,0 +1,144 @@
+/*
+ * 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_SESSION_PHONE_CALL_H_
+#define TALK_SESSION_PHONE_CALL_H_
+
+#include <string>
+#include <map>
+#include <vector>
+#include <deque>
+#include "talk/base/messagequeue.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/xmpp/jid.h"
+#include "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/voicechannel.h"
+
+namespace cricket {
+
+class MediaSessionClient;
+struct CallOptions;
+
+class Call : public talk_base::MessageHandler, public sigslot::has_slots<> {
+ public:
+ Call(MediaSessionClient* session_client);
+ ~Call();
+
+ Session *InitiateSession(const buzz::Jid &jid, const CallOptions& options);
+ void AcceptSession(BaseSession *session, const CallOptions& options);
+ void RejectSession(BaseSession *session);
+ void TerminateSession(BaseSession *session);
+ void Terminate();
+ void SetLocalRenderer(VideoRenderer* renderer);
+ void SetVideoRenderer(BaseSession *session, uint32 ssrc,
+ VideoRenderer* renderer);
+ void AddStream(BaseSession *session, uint32 voice_ssrc, uint32 video_ssrc);
+ void RemoveStream(BaseSession *session, uint32 voice_ssrc, uint32 video_ssrc);
+ void StartConnectionMonitor(BaseSession *session, int cms);
+ void StopConnectionMonitor(BaseSession *session);
+ void StartAudioMonitor(BaseSession *session, int cms);
+ void StopAudioMonitor(BaseSession *session);
+ void Mute(bool mute);
+ void PressDTMF(int event);
+
+ const std::vector<Session *> &sessions();
+ uint32 id();
+ bool video() const { return video_; }
+ bool muted() const { return muted_; }
+
+ // Setting this to false will cause the call to have a longer timeout and
+ // for the SignalSetupToCallVoicemail to never fire.
+ void set_send_to_voicemail(bool send_to_voicemail) {
+ send_to_voicemail_ = send_to_voicemail;
+ }
+ bool send_to_voicemail() { return send_to_voicemail_; }
+
+ // Sets a flag on the chatapp that will redirect the call to voicemail once
+ // the call has been terminated
+ sigslot::signal0<> SignalSetupToCallVoicemail;
+ sigslot::signal2<Call *, Session *> SignalAddSession;
+ sigslot::signal2<Call *, Session *> SignalRemoveSession;
+ sigslot::signal3<Call *, BaseSession *, BaseSession::State>
+ SignalSessionState;
+ sigslot::signal3<Call *, BaseSession *, Session::Error>
+ SignalSessionError;
+ sigslot::signal3<Call *, Session *, const std::string &>
+ SignalReceivedTerminateReason;
+ sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
+ SignalConnectionMonitor;
+ sigslot::signal2<Call *, const VoiceMediaInfo&> SignalMediaMonitor;
+ sigslot::signal2<Call *, const AudioInfo&> SignalAudioMonitor;
+ sigslot::signal2<Call *, const std::vector<ConnectionInfo> &>
+ SignalVideoConnectionMonitor;
+ sigslot::signal2<Call *, const VideoMediaInfo&> SignalVideoMediaMonitor;
+
+ private:
+ void OnMessage(talk_base::Message *message);
+ void OnSessionState(BaseSession *session, BaseSession::State state);
+ void OnSessionError(BaseSession *session, Session::Error error);
+ void OnReceivedTerminateReason(Session *session, const std::string &reason);
+ void IncomingSession(Session *session, const SessionDescription* offer);
+ // Returns true on success.
+ bool AddSession(Session *session, const SessionDescription* offer);
+ void RemoveSession(Session *session);
+ void EnableChannels(bool enable);
+ void Join(Call *call, bool enable);
+ void OnConnectionMonitor(VoiceChannel *channel,
+ const std::vector<ConnectionInfo> &infos);
+ void OnMediaMonitor(VoiceChannel *channel, const VoiceMediaInfo& info);
+ void OnAudioMonitor(VoiceChannel *channel, const AudioInfo& info);
+ void OnConnectionMonitor(VideoChannel *channel,
+ const std::vector<ConnectionInfo> &infos);
+ void OnMediaMonitor(VideoChannel *channel, const VideoMediaInfo& info);
+ VoiceChannel* GetVoiceChannel(BaseSession* session);
+ VideoChannel* GetVideoChannel(BaseSession* session);
+ void ContinuePlayDTMF();
+
+ uint32 id_;
+ MediaSessionClient *session_client_;
+ std::vector<Session *> sessions_;
+ std::map<std::string, VoiceChannel *> voice_channel_map_;
+ std::map<std::string, VideoChannel *> video_channel_map_;
+ VideoRenderer* local_renderer_;
+ bool video_;
+ bool muted_;
+ bool send_to_voicemail_;
+
+ // DTMF tones have to be queued up so that we don't flood the call. We
+ // keep a deque (doubely ended queue) of them around. While one is playing we
+ // set the playing_dtmf_ bit and schedule a message in XX msec to clear that
+ // bit or start the next tone playing.
+ std::deque<int> queued_dtmf_;
+ bool playing_dtmf_;
+
+ friend class MediaSessionClient;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_CALL_H_
diff --git a/talk/session/phone/channel.cc b/talk/session/phone/channel.cc
new file mode 100644
index 0000000..1083ec7
--- /dev/null
+++ b/talk/session/phone/channel.cc
@@ -0,0 +1,1164 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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/session/phone/channel.h"
+
+#include "talk/base/buffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediasessionclient.h"
+#include "talk/session/phone/mediasink.h"
+#include "talk/session/phone/rtcpmuxfilter.h"
+
+namespace cricket {
+
+struct PacketMessageData : public talk_base::MessageData {
+ talk_base::Buffer packet;
+};
+
+struct VoiceChannelErrorMessageData : public talk_base::MessageData {
+ VoiceChannelErrorMessageData(uint32 in_ssrc,
+ VoiceMediaChannel::Error in_error)
+ : ssrc(in_ssrc),
+ error(in_error) {}
+ uint32 ssrc;
+ VoiceMediaChannel::Error error;
+};
+
+struct VideoChannelErrorMessageData : public talk_base::MessageData {
+ VideoChannelErrorMessageData(uint32 in_ssrc,
+ VideoMediaChannel::Error in_error)
+ : ssrc(in_ssrc),
+ error(in_error) {}
+ uint32 ssrc;
+ VideoMediaChannel::Error error;
+};
+
+static const char* PacketType(bool rtcp) {
+ return (!rtcp) ? "RTP" : "RTCP";
+}
+
+static bool ValidPacket(bool rtcp, const talk_base::Buffer* packet) {
+ // Check the packet size. We could check the header too if needed.
+ return (packet &&
+ packet->length() >= (!rtcp ? kMinRtpPacketLen : kMinRtcpPacketLen) &&
+ packet->length() <= kMaxRtpPacketLen);
+}
+
+static uint16 GetRtpSeqNum(const talk_base::Buffer* packet) {
+ return (packet->length() >= kMinRtpPacketLen) ?
+ talk_base::GetBE16(packet->data() + 2) : 0;
+}
+
+static uint32 GetRtpSsrc(const talk_base::Buffer* packet) {
+ return (packet->length() >= kMinRtpPacketLen) ?
+ talk_base::GetBE32(packet->data() + 8) : 0;
+}
+
+static int GetRtcpType(const talk_base::Buffer* packet) {
+ return (packet->length() >= kMinRtcpPacketLen) ?
+ static_cast<int>(packet->data()[1]) : 0;
+}
+
+BaseChannel::BaseChannel(talk_base::Thread* thread, MediaEngine* media_engine,
+ MediaChannel* media_channel, BaseSession* session,
+ const std::string& content_name,
+ TransportChannel* transport_channel)
+ : worker_thread_(thread),
+ media_engine_(media_engine),
+ session_(session),
+ media_channel_(media_channel),
+ received_media_sink_(NULL),
+ sent_media_sink_(NULL),
+ content_name_(content_name),
+ transport_channel_(transport_channel),
+ rtcp_transport_channel_(NULL),
+ enabled_(false),
+ writable_(false),
+ has_codec_(false),
+ muted_(false) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ media_channel_->SetInterface(this);
+ transport_channel_->SignalWritableState.connect(
+ this, &BaseChannel::OnWritableState);
+ transport_channel_->SignalReadPacket.connect(
+ this, &BaseChannel::OnChannelRead);
+
+ LOG(LS_INFO) << "Created channel";
+
+ session->SignalState.connect(this, &BaseChannel::OnSessionState);
+}
+
+BaseChannel::~BaseChannel() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ StopConnectionMonitor();
+ FlushRtcpMessages(); // Send any outstanding RTCP packets.
+ Clear(); // eats any outstanding messages or packets
+ // We must destroy the media channel before the transport channel, otherwise
+ // the media channel may try to send on the dead transport channel. NULLing
+ // is not an effective strategy since the sends will come on another thread.
+ delete media_channel_;
+ set_rtcp_transport_channel(NULL);
+ if (transport_channel_ != NULL)
+ session_->DestroyChannel(content_name_, transport_channel_->name());
+ LOG(LS_INFO) << "Destroyed channel";
+}
+
+bool BaseChannel::Enable(bool enable) {
+ // Can be called from thread other than worker thread
+ Send(enable ? MSG_ENABLE : MSG_DISABLE);
+ return true;
+}
+
+bool BaseChannel::Mute(bool mute) {
+ // Can be called from thread other than worker thread
+ Send(mute ? MSG_MUTE : MSG_UNMUTE);
+ return true;
+}
+
+bool BaseChannel::RemoveStream(uint32 ssrc) {
+ StreamMessageData data(ssrc, 0);
+ Send(MSG_REMOVESTREAM, &data);
+ return true;
+}
+
+bool BaseChannel::SetRtcpCName(const std::string& cname) {
+ SetRtcpCNameData data(cname);
+ Send(MSG_SETRTCPCNAME, &data);
+ return data.result;
+}
+
+bool BaseChannel::SetLocalContent(const MediaContentDescription* content,
+ ContentAction action) {
+ SetContentData data(content, action);
+ Send(MSG_SETLOCALCONTENT, &data);
+ return data.result;
+}
+
+bool BaseChannel::SetRemoteContent(const MediaContentDescription* content,
+ ContentAction action) {
+ SetContentData data(content, action);
+ Send(MSG_SETREMOTECONTENT, &data);
+ return data.result;
+}
+
+bool BaseChannel::SetMaxSendBandwidth(int max_bandwidth) {
+ SetBandwidthData data(max_bandwidth);
+ Send(MSG_SETMAXSENDBANDWIDTH, &data);
+ return data.result;
+}
+
+void BaseChannel::StartConnectionMonitor(int cms) {
+ socket_monitor_.reset(new SocketMonitor(transport_channel_,
+ worker_thread(),
+ talk_base::Thread::Current()));
+ socket_monitor_->SignalUpdate.connect(
+ this, &BaseChannel::OnConnectionMonitorUpdate);
+ socket_monitor_->Start(cms);
+}
+
+void BaseChannel::StopConnectionMonitor() {
+ if (socket_monitor_.get()) {
+ socket_monitor_->Stop();
+ socket_monitor_.reset();
+ }
+}
+
+void BaseChannel::set_rtcp_transport_channel(TransportChannel* channel) {
+ if (rtcp_transport_channel_ != channel) {
+ if (rtcp_transport_channel_) {
+ session_->DestroyChannel(content_name_, rtcp_transport_channel_->name());
+ }
+ rtcp_transport_channel_ = channel;
+ if (rtcp_transport_channel_) {
+ rtcp_transport_channel_->SignalWritableState.connect(
+ this, &BaseChannel::OnWritableState);
+ rtcp_transport_channel_->SignalReadPacket.connect(
+ this, &BaseChannel::OnChannelRead);
+ }
+ }
+}
+
+bool BaseChannel::SendPacket(talk_base::Buffer* packet) {
+ return SendPacket(false, packet);
+}
+
+bool BaseChannel::SendRtcp(talk_base::Buffer* packet) {
+ return SendPacket(true, packet);
+}
+
+int BaseChannel::SetOption(SocketType type, talk_base::Socket::Option opt,
+ int value) {
+ switch (type) {
+ case ST_RTP: return transport_channel_->SetOption(opt, value);
+ case ST_RTCP: return rtcp_transport_channel_->SetOption(opt, value);
+ default: return -1;
+ }
+}
+
+void BaseChannel::OnWritableState(TransportChannel* channel) {
+ ASSERT(channel == transport_channel_ || channel == rtcp_transport_channel_);
+ if (transport_channel_->writable()
+ && (!rtcp_transport_channel_ || rtcp_transport_channel_->writable())) {
+ ChannelWritable_w();
+ } else {
+ ChannelNotWritable_w();
+ }
+}
+
+void BaseChannel::OnChannelRead(TransportChannel* channel,
+ const char* data, size_t len) {
+ // OnChannelRead gets called from P2PSocket; now pass data to MediaEngine
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ talk_base::Buffer packet(data, len);
+ // When using RTCP multiplexing we might get RTCP packets on the RTP
+ // transport. We feed RTP traffic into the demuxer to determine if it is RTCP.
+ bool rtcp = (channel == rtcp_transport_channel_ ||
+ rtcp_mux_filter_.DemuxRtcp(packet.data(), packet.length()));
+ HandlePacket(rtcp, &packet);
+}
+
+bool BaseChannel::SendPacket(bool rtcp, talk_base::Buffer* packet) {
+ // SendPacket gets called from MediaEngine, typically on an encoder thread.
+ // If the thread is not our worker thread, we will post to our worker
+ // so that the real work happens on our worker. This avoids us having to
+ // synchronize access to all the pieces of the send path, including
+ // SRTP and the inner workings of the transport channels.
+ // The only downside is that we can't return a proper failure code if
+ // needed. Since UDP is unreliable anyway, this should be a non-issue.
+ if (talk_base::Thread::Current() != worker_thread_) {
+ // Avoid a copy by transferring the ownership of the packet data.
+ int message_id = (!rtcp) ? MSG_RTPPACKET : MSG_RTCPPACKET;
+ PacketMessageData* data = new PacketMessageData;
+ packet->TransferTo(&data->packet);
+ worker_thread_->Post(this, message_id, data);
+ return true;
+ }
+
+ // Make sure we have a place to send this packet before doing anything.
+ // (We might get RTCP packets that we don't intend to send.)
+ // If we've negotiated RTCP mux, send RTCP over the RTP transport.
+ TransportChannel* channel = (!rtcp || rtcp_mux_filter_.IsActive()) ?
+ transport_channel_ : rtcp_transport_channel_;
+ if (!channel) {
+ return false;
+ }
+
+ // Protect ourselves against crazy data.
+ if (!ValidPacket(rtcp, packet)) {
+ LOG(LS_ERROR) << "Dropping outgoing " << content_name_ << " "
+ << PacketType(rtcp) << " packet: wrong size="
+ << packet->length();
+ return false;
+ }
+
+ // Push the packet down to the media sink.
+ // Need to do this before protecting the packet.
+ {
+ talk_base::CritScope cs(&sink_critical_section_);
+ if (sent_media_sink_) {
+ if (!rtcp) {
+ sent_media_sink_->OnRtpPacket(packet->data(), packet->length());
+ } else {
+ sent_media_sink_->OnRtcpPacket(packet->data(), packet->length());
+ }
+ }
+ }
+
+ // Protect if needed.
+ if (srtp_filter_.IsActive()) {
+ bool res;
+ char* data = packet->data();
+ int len = packet->length();
+ if (!rtcp) {
+ res = srtp_filter_.ProtectRtp(data, len, packet->capacity(), &len);
+ if (!res) {
+ LOG(LS_ERROR) << "Failed to protect " << content_name_
+ << " RTP packet: size=" << len
+ << ", seqnum=" << GetRtpSeqNum(packet)
+ << ", SSRC=" << GetRtpSsrc(packet);
+ return false;
+ }
+ } else {
+ res = srtp_filter_.ProtectRtcp(data, len, packet->capacity(), &len);
+ if (!res) {
+ LOG(LS_ERROR) << "Failed to protect " << content_name_
+ << " RTCP packet: size=" << len
+ << ", type=" << GetRtcpType(packet);
+ return false;
+ }
+ }
+
+ // Update the length of the packet now that we've added the auth tag.
+ packet->SetLength(len);
+ }
+
+ // Bon voyage.
+ return (channel->SendPacket(packet->data(), packet->length())
+ == static_cast<int>(packet->length()));
+}
+
+void BaseChannel::HandlePacket(bool rtcp, talk_base::Buffer* packet) {
+ // Protect ourselvs against crazy data.
+ if (!ValidPacket(rtcp, packet)) {
+ LOG(LS_ERROR) << "Dropping incoming " << content_name_ << " "
+ << PacketType(rtcp) << " packet: wrong size="
+ << packet->length();
+ return;
+ }
+
+ // Unprotect the packet, if needed.
+ if (srtp_filter_.IsActive()) {
+ char* data = packet->data();
+ int len = packet->length();
+ bool res;
+ if (!rtcp) {
+ res = srtp_filter_.UnprotectRtp(data, len, &len);
+ if (!res) {
+ LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+ << " RTP packet: size=" << len
+ << ", seqnum=" << GetRtpSeqNum(packet)
+ << ", SSRC=" << GetRtpSsrc(packet);
+ return;
+ }
+ } else {
+ res = srtp_filter_.UnprotectRtcp(data, len, &len);
+ if (!res) {
+ LOG(LS_ERROR) << "Failed to unprotect " << content_name_
+ << " RTCP packet: size=" << len
+ << ", type=" << GetRtcpType(packet);
+ return;
+ }
+ }
+
+ packet->SetLength(len);
+ }
+
+ // Push it down to the media channel.
+ if (!rtcp) {
+ media_channel_->OnPacketReceived(packet);
+ } else {
+ media_channel_->OnRtcpReceived(packet);
+ }
+
+ // Push it down to the media sink.
+ {
+ talk_base::CritScope cs(&sink_critical_section_);
+ if (received_media_sink_) {
+ if (!rtcp) {
+ received_media_sink_->OnRtpPacket(packet->data(), packet->length());
+ } else {
+ received_media_sink_->OnRtcpPacket(packet->data(), packet->length());
+ }
+ }
+ }
+}
+
+void BaseChannel::OnSessionState(BaseSession* session,
+ BaseSession::State state) {
+ const MediaContentDescription* content = NULL;
+ switch (state) {
+ case Session::STATE_SENTINITIATE:
+ content = GetFirstContent(session->local_description());
+ if (content && !SetLocalContent(content, CA_OFFER)) {
+ LOG(LS_ERROR) << "Failure in SetLocalContent with CA_OFFER";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ }
+ break;
+ case Session::STATE_SENTACCEPT:
+ content = GetFirstContent(session->local_description());
+ if (content && !SetLocalContent(content, CA_ANSWER)) {
+ LOG(LS_ERROR) << "Failure in SetLocalContent with CA_ANSWER";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ }
+ break;
+ case Session::STATE_RECEIVEDINITIATE:
+ content = GetFirstContent(session->remote_description());
+ if (content && !SetRemoteContent(content, CA_OFFER)) {
+ LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_OFFER";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ }
+ break;
+ case Session::STATE_RECEIVEDACCEPT:
+ content = GetFirstContent(session->remote_description());
+ if (content && !SetRemoteContent(content, CA_ANSWER)) {
+ LOG(LS_ERROR) << "Failure in SetRemoteContent with CA_ANSWER";
+ session->SetError(BaseSession::ERROR_CONTENT);
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void BaseChannel::EnableMedia_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (enabled_)
+ return;
+
+ LOG(LS_INFO) << "Channel enabled";
+ enabled_ = true;
+ ChangeState();
+}
+
+void BaseChannel::DisableMedia_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (!enabled_)
+ return;
+
+ LOG(LS_INFO) << "Channel disabled";
+ enabled_ = false;
+ ChangeState();
+}
+
+void BaseChannel::MuteMedia_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (muted_)
+ return;
+
+ if (media_channel()->Mute(true)) {
+ LOG(LS_INFO) << "Channel muted";
+ muted_ = true;
+ }
+}
+
+void BaseChannel::UnmuteMedia_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (!muted_)
+ return;
+
+ if (media_channel()->Mute(false)) {
+ LOG(LS_INFO) << "Channel unmuted";
+ muted_ = false;
+ }
+}
+
+void BaseChannel::ChannelWritable_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (writable_)
+ return;
+ LOG(LS_INFO) << "Channel socket writable ("
+ << transport_channel_->name().c_str() << ")";
+ writable_ = true;
+ ChangeState();
+}
+
+void BaseChannel::ChannelNotWritable_w() {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ if (!writable_)
+ return;
+
+ LOG(LS_INFO) << "Channel socket not writable ("
+ << transport_channel_->name().c_str() << ")";
+ writable_ = false;
+ ChangeState();
+}
+
+// Sets the maximum video bandwidth for automatic bandwidth adjustment.
+bool BaseChannel::SetMaxSendBandwidth_w(int max_bandwidth) {
+ return media_channel()->SetSendBandwidth(true, max_bandwidth);
+}
+
+bool BaseChannel::SetRtcpCName_w(const std::string& cname) {
+ return media_channel()->SetRtcpCName(cname);
+}
+
+bool BaseChannel::SetSrtp_w(const std::vector<CryptoParams>& cryptos,
+ ContentAction action, ContentSource src) {
+ bool ret;
+ if (action == CA_OFFER) {
+ ret = srtp_filter_.SetOffer(cryptos, src);
+ } else if (action == CA_ANSWER) {
+ ret = srtp_filter_.SetAnswer(cryptos, src);
+ } else {
+ // CA_UPDATE, no crypto params.
+ ret = true;
+ }
+ return ret;
+}
+
+bool BaseChannel::SetRtcpMux_w(bool enable, ContentAction action,
+ ContentSource src) {
+ bool ret;
+ if (action == CA_OFFER) {
+ ret = rtcp_mux_filter_.SetOffer(enable, src);
+ } else if (action == CA_ANSWER) {
+ ret = rtcp_mux_filter_.SetAnswer(enable, src);
+ if (ret && rtcp_mux_filter_.IsActive()) {
+ // We activated RTCP mux, close down the RTCP transport.
+ set_rtcp_transport_channel(NULL);
+ // If the RTP transport is already writable, then so are we.
+ if (transport_channel_->writable()) {
+ ChannelWritable_w();
+ }
+ }
+ } else {
+ // CA_UPDATE, no RTCP mux info.
+ ret = true;
+ }
+ return ret;
+}
+
+void BaseChannel::OnMessage(talk_base::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_ENABLE:
+ EnableMedia_w();
+ break;
+ case MSG_DISABLE:
+ DisableMedia_w();
+ break;
+
+ case MSG_MUTE:
+ MuteMedia_w();
+ break;
+ case MSG_UNMUTE:
+ UnmuteMedia_w();
+ break;
+
+ case MSG_SETRTCPCNAME: {
+ SetRtcpCNameData* data = static_cast<SetRtcpCNameData*>(pmsg->pdata);
+ data->result = SetRtcpCName_w(data->cname);
+ break;
+ }
+
+ case MSG_SETLOCALCONTENT: {
+ SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+ data->result = SetLocalContent_w(data->content, data->action);
+ break;
+ }
+ case MSG_SETREMOTECONTENT: {
+ SetContentData* data = static_cast<SetContentData*>(pmsg->pdata);
+ data->result = SetRemoteContent_w(data->content, data->action);
+ break;
+ }
+
+ case MSG_REMOVESTREAM: {
+ StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+ RemoveStream_w(data->ssrc1);
+ break;
+ }
+
+ case MSG_SETMAXSENDBANDWIDTH: {
+ SetBandwidthData* data = static_cast<SetBandwidthData*>(pmsg->pdata);
+ data->result = SetMaxSendBandwidth_w(data->value);
+ break;
+ }
+
+ case MSG_RTPPACKET:
+ case MSG_RTCPPACKET: {
+ PacketMessageData* data = static_cast<PacketMessageData*>(pmsg->pdata);
+ SendPacket(pmsg->message_id == MSG_RTCPPACKET, &data->packet);
+ delete data; // because it is Posted
+ break;
+ }
+ }
+}
+
+void BaseChannel::Send(uint32 id, talk_base::MessageData *pdata) {
+ worker_thread_->Send(this, id, pdata);
+}
+
+void BaseChannel::Post(uint32 id, talk_base::MessageData *pdata) {
+ worker_thread_->Post(this, id, pdata);
+}
+
+void BaseChannel::PostDelayed(int cmsDelay, uint32 id,
+ talk_base::MessageData *pdata) {
+ worker_thread_->PostDelayed(cmsDelay, this, id, pdata);
+}
+
+void BaseChannel::Clear(uint32 id, talk_base::MessageList* removed) {
+ worker_thread_->Clear(this, id, removed);
+}
+
+void BaseChannel::FlushRtcpMessages() {
+ // Flush all remaining RTCP messages. This should only be called in
+ // destructor.
+ ASSERT(talk_base::Thread::Current() == worker_thread_);
+ talk_base::MessageList rtcp_messages;
+ Clear(MSG_RTCPPACKET, &rtcp_messages);
+ for (talk_base::MessageList::iterator it = rtcp_messages.begin();
+ it != rtcp_messages.end(); ++it) {
+ Send(MSG_RTCPPACKET, it->pdata);
+ }
+}
+
+VoiceChannel::VoiceChannel(talk_base::Thread* thread,
+ MediaEngine* media_engine,
+ VoiceMediaChannel* media_channel,
+ BaseSession* session,
+ const std::string& content_name,
+ bool rtcp)
+ : BaseChannel(thread, media_engine, media_channel, session, content_name,
+ session->CreateChannel(content_name, "rtp")),
+ received_media_(false) {
+ if (rtcp) {
+ set_rtcp_transport_channel(session->CreateChannel(content_name, "rtcp"));
+ }
+ // Can't go in BaseChannel because certain session states will
+ // trigger pure virtual functions, such as GetFirstContent().
+ OnSessionState(session, session->state());
+
+ media_channel->SignalMediaError.connect(
+ this, &VoiceChannel::OnVoiceChannelError);
+}
+
+VoiceChannel::~VoiceChannel() {
+ StopAudioMonitor();
+ StopMediaMonitor();
+ // this can't be done in the base class, since it calls a virtual
+ DisableMedia_w();
+}
+
+bool VoiceChannel::AddStream(uint32 ssrc) {
+ StreamMessageData data(ssrc, 0);
+ Send(MSG_ADDSTREAM, &data);
+ return true;
+}
+
+bool VoiceChannel::SetRingbackTone(const void* buf, int len) {
+ SetRingbackToneMessageData data(buf, len);
+ Send(MSG_SETRINGBACKTONE, &data);
+ return true;
+}
+
+// TODO: Handle early media the right way. We should get an explicit
+// ringing message telling us to start playing local ringback, which we cancel
+// if any early media actually arrives. For now, we do the opposite, which is
+// to wait 1 second for early media, and start playing local ringback if none
+// arrives.
+void VoiceChannel::SetEarlyMedia(bool enable) {
+ if (enable) {
+ // Start the early media timeout
+ PostDelayed(kEarlyMediaTimeout, MSG_EARLYMEDIATIMEOUT);
+ } else {
+ // Stop the timeout if currently going.
+ Clear(MSG_EARLYMEDIATIMEOUT);
+ }
+}
+
+bool VoiceChannel::PlayRingbackTone(bool play, bool loop) {
+ PlayRingbackToneMessageData data(play, loop);
+ Send(MSG_PLAYRINGBACKTONE, &data);
+ return data.result;
+}
+
+bool VoiceChannel::PressDTMF(int digit, bool playout) {
+ DtmfMessageData data(digit, playout);
+ Send(MSG_PRESSDTMF, &data);
+ return data.result;
+}
+
+void VoiceChannel::StartMediaMonitor(int cms) {
+ media_monitor_.reset(new VoiceMediaMonitor(media_channel(), worker_thread(),
+ talk_base::Thread::Current()));
+ media_monitor_->SignalUpdate.connect(
+ this, &VoiceChannel::OnMediaMonitorUpdate);
+ media_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopMediaMonitor() {
+ if (media_monitor_.get()) {
+ media_monitor_->Stop();
+ media_monitor_->SignalUpdate.disconnect(this);
+ media_monitor_.reset();
+ }
+}
+
+void VoiceChannel::StartAudioMonitor(int cms) {
+ audio_monitor_.reset(new AudioMonitor(this, talk_base::Thread::Current()));
+ audio_monitor_
+ ->SignalUpdate.connect(this, &VoiceChannel::OnAudioMonitorUpdate);
+ audio_monitor_->Start(cms);
+}
+
+void VoiceChannel::StopAudioMonitor() {
+ if (audio_monitor_.get()) {
+ audio_monitor_->Stop();
+ audio_monitor_.reset();
+ }
+}
+
+int VoiceChannel::GetInputLevel_w() {
+ return media_engine()->GetInputLevel();
+}
+
+int VoiceChannel::GetOutputLevel_w() {
+ return media_channel()->GetOutputLevel();
+}
+
+void VoiceChannel::GetActiveStreams_w(AudioInfo::StreamList* actives) {
+ media_channel()->GetActiveStreams(actives);
+}
+
+void VoiceChannel::OnChannelRead(TransportChannel* channel,
+ const char* data, size_t len) {
+ BaseChannel::OnChannelRead(channel, data, len);
+
+ // Set a flag when we've received an RTP packet. If we're waiting for early
+ // media, this will disable the timeout.
+ // If we were playing out our local ringback, make sure it is stopped to
+ // prevent it from interfering with the incoming media.
+ if (!received_media_) {
+ if (!PlayRingbackTone_w(false, false)) {
+ LOG(LS_ERROR) << "Failed to stop ringback tone.";
+ SendLastMediaError();
+ }
+ }
+}
+
+void VoiceChannel::ChangeState() {
+ // render incoming data if we are the active call
+ // we receive data on the default channel and multiplexed streams
+ bool recv = enabled();
+ if (!media_channel()->SetPlayout(recv)) {
+ SendLastMediaError();
+ }
+
+ // send outgoing data if we are the active call, have the
+ // remote party's codec, and have a writable transport
+ // we only send data on the default channel
+ bool send = enabled() && has_codec() && writable();
+ SendFlags send_flag = send ? SEND_MICROPHONE : SEND_NOTHING;
+ if (!media_channel()->SetSend(send_flag)) {
+ LOG(LS_ERROR) << "Failed to SetSend " << send_flag << " on voice channel";
+ SendLastMediaError();
+ }
+
+ LOG(LS_INFO) << "Changing voice state, recv=" << recv << " send=" << send;
+}
+
+const MediaContentDescription* VoiceChannel::GetFirstContent(
+ const SessionDescription* sdesc) {
+ const ContentInfo* cinfo = GetFirstAudioContent(sdesc);
+ if (cinfo == NULL)
+ return NULL;
+
+ return static_cast<const MediaContentDescription*>(cinfo->description);
+}
+
+bool VoiceChannel::SetLocalContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ LOG(LS_INFO) << "Setting local voice description";
+
+ const AudioContentDescription* audio =
+ static_cast<const AudioContentDescription*>(content);
+ ASSERT(audio != NULL);
+
+ bool ret;
+ // set SRTP
+ ret = SetSrtp_w(audio->cryptos(), action, CS_LOCAL);
+ // set RTCP mux
+ if (ret) {
+ ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_LOCAL);
+ }
+ // set payload type and config for voice codecs
+ if (ret) {
+ ret = media_channel()->SetRecvCodecs(audio->codecs());
+ }
+ return ret;
+}
+
+bool VoiceChannel::SetRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ LOG(LS_INFO) << "Setting remote voice description";
+
+ const AudioContentDescription* audio =
+ static_cast<const AudioContentDescription*>(content);
+ ASSERT(audio != NULL);
+
+ bool ret;
+ // set the sending SSRC, if the remote side gave us one
+ if (audio->ssrc_set()) {
+ media_channel()->SetSendSsrc(audio->ssrc());
+ }
+ // set SRTP
+ ret = SetSrtp_w(audio->cryptos(), action, CS_REMOTE);
+ // set RTCP mux
+ if (ret) {
+ ret = SetRtcpMux_w(audio->rtcp_mux(), action, CS_REMOTE);
+ }
+ // set codecs and payload types
+ if (ret) {
+ ret = media_channel()->SetSendCodecs(audio->codecs());
+ }
+
+ int audio_options = 0;
+ if (audio->conference_mode()) {
+ audio_options |= OPT_CONFERENCE;
+ }
+ if (!media_channel()->SetOptions(audio_options)) {
+ // Log an error on failure, but don't abort the call.
+ LOG(LS_ERROR) << "Failed to set voice channel options";
+ }
+
+ // update state
+ if (ret) {
+ set_has_codec(true);
+ ChangeState();
+ }
+ return ret;
+}
+
+void VoiceChannel::AddStream_w(uint32 ssrc) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ media_channel()->AddStream(ssrc);
+}
+
+void VoiceChannel::RemoveStream_w(uint32 ssrc) {
+ media_channel()->RemoveStream(ssrc);
+}
+
+void VoiceChannel::SetRingbackTone_w(const void* buf, int len) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ media_channel()->SetRingbackTone(static_cast<const char*>(buf), len);
+}
+
+bool VoiceChannel::PlayRingbackTone_w(bool play, bool loop) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ if (play) {
+ LOG(LS_INFO) << "Playing ringback tone, loop=" << loop;
+ } else {
+ LOG(LS_INFO) << "Stopping ringback tone";
+ }
+ return media_channel()->PlayRingbackTone(play, loop);
+}
+
+void VoiceChannel::HandleEarlyMediaTimeout() {
+ // This occurs on the main thread, not the worker thread.
+ if (!received_media_) {
+ LOG(LS_INFO) << "No early media received before timeout";
+ SignalEarlyMediaTimeout(this);
+ }
+}
+
+bool VoiceChannel::PressDTMF_w(int digit, bool playout) {
+ if (!enabled() || !writable()) {
+ return false;
+ }
+
+ return media_channel()->PressDTMF(digit, playout);
+}
+
+void VoiceChannel::OnMessage(talk_base::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_ADDSTREAM: {
+ StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+ AddStream_w(data->ssrc1);
+ break;
+ }
+ case MSG_SETRINGBACKTONE: {
+ SetRingbackToneMessageData* data =
+ static_cast<SetRingbackToneMessageData*>(pmsg->pdata);
+ SetRingbackTone_w(data->buf, data->len);
+ break;
+ }
+ case MSG_PLAYRINGBACKTONE: {
+ PlayRingbackToneMessageData* data =
+ static_cast<PlayRingbackToneMessageData*>(pmsg->pdata);
+ data->result = PlayRingbackTone_w(data->play, data->loop);
+ break;
+ }
+ case MSG_EARLYMEDIATIMEOUT:
+ HandleEarlyMediaTimeout();
+ break;
+ case MSG_PRESSDTMF: {
+ DtmfMessageData* data = static_cast<DtmfMessageData*>(pmsg->pdata);
+ data->result = PressDTMF_w(data->digit, data->playout);
+ break;
+ }
+ case MSG_CHANNEL_ERROR: {
+ VoiceChannelErrorMessageData* data =
+ static_cast<VoiceChannelErrorMessageData*>(pmsg->pdata);
+ SignalMediaError(this, data->ssrc, data->error);
+ delete data;
+ break;
+ }
+
+ default:
+ BaseChannel::OnMessage(pmsg);
+ break;
+ }
+}
+
+void VoiceChannel::OnConnectionMonitorUpdate(
+ SocketMonitor* monitor, const std::vector<ConnectionInfo>& infos) {
+ SignalConnectionMonitor(this, infos);
+}
+
+void VoiceChannel::OnMediaMonitorUpdate(
+ VoiceMediaChannel* media_channel, const VoiceMediaInfo& info) {
+ ASSERT(media_channel == this->media_channel());
+ SignalMediaMonitor(this, info);
+}
+
+void VoiceChannel::OnAudioMonitorUpdate(AudioMonitor* monitor,
+ const AudioInfo& info) {
+ SignalAudioMonitor(this, info);
+}
+
+void VoiceChannel::OnVoiceChannelError(
+ uint32 ssrc, VoiceMediaChannel::Error error) {
+ VoiceChannelErrorMessageData *data = new VoiceChannelErrorMessageData(
+ ssrc, error);
+ signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+VideoChannel::VideoChannel(talk_base::Thread* thread,
+ MediaEngine* media_engine,
+ VideoMediaChannel* media_channel,
+ BaseSession* session,
+ const std::string& content_name,
+ bool rtcp,
+ VoiceChannel* voice_channel)
+ : BaseChannel(thread, media_engine, media_channel, session, content_name,
+ session->CreateChannel(content_name, "video_rtp")),
+ voice_channel_(voice_channel), renderer_(NULL) {
+ if (rtcp) {
+ set_rtcp_transport_channel(
+ session->CreateChannel(content_name, "video_rtcp"));
+ }
+ // Can't go in BaseChannel because certain session states will
+ // trigger pure virtual functions, such as GetFirstContent()
+ OnSessionState(session, session->state());
+
+ media_channel->SignalMediaError.connect(
+ this, &VideoChannel::OnVideoChannelError);
+}
+
+void VoiceChannel::SendLastMediaError() {
+ uint32 ssrc;
+ VoiceMediaChannel::Error error;
+ media_channel()->GetLastMediaError(&ssrc, &error);
+ SignalMediaError(this, ssrc, error);
+}
+
+VideoChannel::~VideoChannel() {
+ StopMediaMonitor();
+ // this can't be done in the base class, since it calls a virtual
+ DisableMedia_w();
+}
+
+bool VideoChannel::AddStream(uint32 ssrc, uint32 voice_ssrc) {
+ StreamMessageData data(ssrc, voice_ssrc);
+ Send(MSG_ADDSTREAM, &data);
+ return true;
+}
+
+bool VideoChannel::SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+ RenderMessageData data(ssrc, renderer);
+ Send(MSG_SETRENDERER, &data);
+ return true;
+}
+
+
+
+bool VideoChannel::SendIntraFrame() {
+ Send(MSG_SENDINTRAFRAME);
+ return true;
+}
+bool VideoChannel::RequestIntraFrame() {
+ Send(MSG_REQUESTINTRAFRAME);
+ return true;
+}
+
+void VideoChannel::ChangeState() {
+ // render incoming data if we are the active call
+ // we receive data on the default channel and multiplexed streams
+ bool recv = enabled();
+ if (!media_channel()->SetRender(recv)) {
+ LOG(LS_ERROR) << "Failed to SetRender on video channel";
+ // TODO: Report error back to server.
+ }
+
+ // send outgoing data if we are the active call, have the
+ // remote party's codec, and have a writable transport
+ // we only send data on the default channel
+ bool send = enabled() && has_codec() && writable();
+ if (!media_channel()->SetSend(send)) {
+ LOG(LS_ERROR) << "Failed to SetSend on video channel";
+ // TODO: Report error back to server.
+ }
+
+ LOG(LS_INFO) << "Changing video state, recv=" << recv << " send=" << send;
+}
+
+void VideoChannel::StartMediaMonitor(int cms) {
+ media_monitor_.reset(new VideoMediaMonitor(media_channel(), worker_thread(),
+ talk_base::Thread::Current()));
+ media_monitor_->SignalUpdate.connect(
+ this, &VideoChannel::OnMediaMonitorUpdate);
+ media_monitor_->Start(cms);
+}
+
+void VideoChannel::StopMediaMonitor() {
+ if (media_monitor_.get()) {
+ media_monitor_->Stop();
+ media_monitor_.reset();
+ }
+}
+
+const MediaContentDescription* VideoChannel::GetFirstContent(
+ const SessionDescription* sdesc) {
+ const ContentInfo* cinfo = GetFirstVideoContent(sdesc);
+ if (cinfo == NULL)
+ return NULL;
+
+ return static_cast<const MediaContentDescription*>(cinfo->description);
+}
+
+bool VideoChannel::SetLocalContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ LOG(LS_INFO) << "Setting local video description";
+
+ const VideoContentDescription* video =
+ static_cast<const VideoContentDescription*>(content);
+ ASSERT(video != NULL);
+
+ bool ret;
+ // set SRTP
+ ret = SetSrtp_w(video->cryptos(), action, CS_LOCAL);
+ // set RTCP mux
+ if (ret) {
+ ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_LOCAL);
+ }
+ // set payload types and config for receiving video
+ if (ret) {
+ ret = media_channel()->SetRecvCodecs(video->codecs());
+ }
+ return ret;
+}
+
+bool VideoChannel::SetRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action) {
+ ASSERT(worker_thread() == talk_base::Thread::Current());
+ LOG(LS_INFO) << "Setting remote video description";
+
+ const VideoContentDescription* video =
+ static_cast<const VideoContentDescription*>(content);
+ ASSERT(video != NULL);
+
+ bool ret;
+ // set the sending SSRC, if the remote side gave us one
+ // TODO: remove this, since it's not needed.
+ if (video->ssrc_set()) {
+ media_channel()->SetSendSsrc(video->ssrc());
+ }
+ // set SRTP
+ ret = SetSrtp_w(video->cryptos(), action, CS_REMOTE);
+ // set RTCP mux
+ if (ret) {
+ ret = SetRtcpMux_w(video->rtcp_mux(), action, CS_REMOTE);
+ }
+ // Set video bandwidth parameters.
+ if (ret) {
+ int bandwidth_bps = video->bandwidth();
+ bool auto_bandwidth = (bandwidth_bps == kAutoBandwidth);
+ ret = media_channel()->SetSendBandwidth(auto_bandwidth, bandwidth_bps);
+ }
+ if (ret) {
+ ret = media_channel()->SetSendCodecs(video->codecs());
+ }
+ media_channel()->SetRtpExtensionHeaders(!video->rtp_headers_disabled());
+ if (ret) {
+ set_has_codec(true);
+ ChangeState();
+ }
+ return ret;
+}
+
+void VideoChannel::AddStream_w(uint32 ssrc, uint32 voice_ssrc) {
+ media_channel()->AddStream(ssrc, voice_ssrc);
+}
+
+void VideoChannel::RemoveStream_w(uint32 ssrc) {
+ media_channel()->RemoveStream(ssrc);
+}
+
+void VideoChannel::SetRenderer_w(uint32 ssrc, VideoRenderer* renderer) {
+ media_channel()->SetRenderer(ssrc, renderer);
+}
+
+
+void VideoChannel::OnMessage(talk_base::Message *pmsg) {
+ switch (pmsg->message_id) {
+ case MSG_ADDSTREAM: {
+ StreamMessageData* data = static_cast<StreamMessageData*>(pmsg->pdata);
+ AddStream_w(data->ssrc1, data->ssrc2);
+ break;
+ }
+ case MSG_SETRENDERER: {
+ RenderMessageData* data = static_cast<RenderMessageData*>(pmsg->pdata);
+ SetRenderer_w(data->ssrc, data->renderer);
+ break;
+ }
+ case MSG_SENDINTRAFRAME:
+ SendIntraFrame_w();
+ break;
+ case MSG_REQUESTINTRAFRAME:
+ RequestIntraFrame_w();
+ break;
+ case MSG_CHANNEL_ERROR: {
+ const VideoChannelErrorMessageData* data =
+ static_cast<VideoChannelErrorMessageData*>(pmsg->pdata);
+ SignalMediaError(this, data->ssrc, data->error);
+ delete data;
+ break;
+ }
+ default:
+ BaseChannel::OnMessage(pmsg);
+ break;
+ }
+}
+
+void VideoChannel::OnConnectionMonitorUpdate(
+ SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos) {
+ SignalConnectionMonitor(this, infos);
+}
+
+void VideoChannel::OnMediaMonitorUpdate(
+ VideoMediaChannel* media_channel, const VideoMediaInfo &info) {
+ ASSERT(media_channel == this->media_channel());
+ SignalMediaMonitor(this, info);
+}
+
+
+void VideoChannel::OnVideoChannelError(uint32 ssrc,
+ VideoMediaChannel::Error error) {
+ VideoChannelErrorMessageData* data = new VideoChannelErrorMessageData(
+ ssrc, error);
+ signaling_thread()->Post(this, MSG_CHANNEL_ERROR, data);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/channel.h b/talk/session/phone/channel.h
new file mode 100644
index 0000000..e7bee83
--- /dev/null
+++ b/talk/session/phone/channel.h
@@ -0,0 +1,441 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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_CHANNEL_H_
+#define TALK_SESSION_PHONE_CHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/asyncudpsocket.h"
+#include "talk/base/criticalsection.h"
+#include "talk/base/network.h"
+#include "talk/base/sigslot.h"
+#include "talk/p2p/client/socketmonitor.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/audiomonitor.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediamonitor.h"
+#include "talk/session/phone/rtcpmuxfilter.h"
+#include "talk/session/phone/srtpfilter.h"
+
+namespace cricket {
+
+class MediaContentDescription;
+class MediaSinkInterface;
+struct CryptoParams;
+
+enum {
+ MSG_ENABLE = 1,
+ MSG_DISABLE = 2,
+ MSG_MUTE = 3,
+ MSG_UNMUTE = 4,
+ MSG_SETREMOTECONTENT = 5,
+ MSG_SETLOCALCONTENT = 6,
+ MSG_EARLYMEDIATIMEOUT = 8,
+ MSG_PRESSDTMF = 9,
+ MSG_SETRENDERER = 10,
+ MSG_ADDSTREAM = 11,
+ MSG_REMOVESTREAM = 12,
+ MSG_SETRINGBACKTONE = 13,
+ MSG_PLAYRINGBACKTONE = 14,
+ MSG_SETMAXSENDBANDWIDTH = 15,
+ MSG_SETRTCPCNAME = 18,
+ MSG_SENDINTRAFRAME = 19,
+ MSG_REQUESTINTRAFRAME = 20,
+ MSG_RTPPACKET = 22,
+ MSG_RTCPPACKET = 23,
+ MSG_CHANNEL_ERROR = 24
+};
+
+// BaseChannel contains logic common to voice and video, including
+// enable/mute, marshaling calls to a worker thread, and
+// connection and media monitors.
+class BaseChannel
+ : public talk_base::MessageHandler, public sigslot::has_slots<>,
+ public MediaChannel::NetworkInterface {
+ public:
+ BaseChannel(talk_base::Thread* thread, MediaEngine* media_engine,
+ MediaChannel* channel, BaseSession* session,
+ const std::string& content_name,
+ TransportChannel* transport_channel);
+ virtual ~BaseChannel();
+
+ talk_base::Thread* worker_thread() const { return worker_thread_; }
+ BaseSession* session() const { return session_; }
+ const std::string& content_name() { return content_name_; }
+ TransportChannel* transport_channel() const {
+ return transport_channel_;
+ }
+ TransportChannel* rtcp_transport_channel() const {
+ return rtcp_transport_channel_;
+ }
+ bool enabled() const { return enabled_; }
+ bool secure() const { return srtp_filter_.IsActive(); }
+
+ // Channel control
+ bool SetRtcpCName(const std::string& cname);
+ bool SetLocalContent(const MediaContentDescription* content,
+ ContentAction action);
+ bool SetRemoteContent(const MediaContentDescription* content,
+ ContentAction action);
+ bool SetMaxSendBandwidth(int max_bandwidth);
+
+ bool Enable(bool enable);
+ bool Mute(bool mute);
+
+ // Multiplexing
+ bool RemoveStream(uint32 ssrc);
+
+ // Monitoring
+ void StartConnectionMonitor(int cms);
+ void StopConnectionMonitor();
+
+ // Set and get media sinks for recording media.
+ void set_received_media_sink(MediaSinkInterface* sink) {
+ talk_base::CritScope cs(&sink_critical_section_);
+ received_media_sink_ = sink;
+ }
+ const MediaSinkInterface* received_media_sink() {
+ talk_base::CritScope cs(&sink_critical_section_);
+ return received_media_sink_;
+ }
+ void set_sent_media_sink(MediaSinkInterface* sink) {
+ talk_base::CritScope cs(&sink_critical_section_);
+ sent_media_sink_ = sink;
+ }
+ const MediaSinkInterface* sent_media_sink() {
+ talk_base::CritScope cs(&sink_critical_section_);
+ return sent_media_sink_;
+ }
+
+ protected:
+ MediaEngine* media_engine() const { return media_engine_; }
+ virtual MediaChannel* media_channel() const { return media_channel_; }
+ void set_rtcp_transport_channel(TransportChannel* transport);
+ bool writable() const { return writable_; }
+ bool has_codec() const { return has_codec_; }
+ void set_has_codec(bool has_codec) { has_codec_ = has_codec; }
+ bool muted() const { return muted_; }
+ talk_base::Thread* signaling_thread() { return session_->signaling_thread(); }
+
+ void Send(uint32 id, talk_base::MessageData *pdata = NULL);
+ void Post(uint32 id, talk_base::MessageData *pdata = NULL);
+ void PostDelayed(int cmsDelay, uint32 id = 0,
+ talk_base::MessageData *pdata = NULL);
+ void Clear(uint32 id = talk_base::MQID_ANY,
+ talk_base::MessageList* removed = NULL);
+ void FlushRtcpMessages();
+
+ // NetworkInterface implementation, called by MediaEngine
+ virtual bool SendPacket(talk_base::Buffer* packet);
+ virtual bool SendRtcp(talk_base::Buffer* packet);
+ virtual int SetOption(SocketType type, talk_base::Socket::Option o, int val);
+
+ // From TransportChannel
+ void OnWritableState(TransportChannel* channel);
+ void OnChannelRead(TransportChannel* channel, const char *data, size_t len);
+
+ bool SendPacket(bool rtcp, talk_base::Buffer* packet);
+ void HandlePacket(bool rtcp, talk_base::Buffer* packet);
+
+ // Setting the send codec based on the remote description.
+ void OnSessionState(BaseSession* session, BaseSession::State state);
+
+ void EnableMedia_w();
+ void DisableMedia_w();
+ void MuteMedia_w();
+ void UnmuteMedia_w();
+ void ChannelWritable_w();
+ void ChannelNotWritable_w();
+
+ struct StreamMessageData : public talk_base::MessageData {
+ StreamMessageData(uint32 s1, uint32 s2) : ssrc1(s1), ssrc2(s2) {}
+ uint32 ssrc1;
+ uint32 ssrc2;
+ };
+ virtual void RemoveStream_w(uint32 ssrc) = 0;
+
+ virtual void ChangeState() = 0;
+
+ struct SetRtcpCNameData : public talk_base::MessageData {
+ explicit SetRtcpCNameData(const std::string& cname)
+ : cname(cname), result(false) {}
+ std::string cname;
+ bool result;
+ };
+ bool SetRtcpCName_w(const std::string& cname);
+
+ struct SetContentData : public talk_base::MessageData {
+ SetContentData(const MediaContentDescription* content,
+ ContentAction action)
+ : content(content), action(action), result(false) {}
+ const MediaContentDescription* content;
+ ContentAction action;
+ bool result;
+ };
+
+ // Gets the content appropriate to the channel (audio or video).
+ virtual const MediaContentDescription* GetFirstContent(
+ const SessionDescription* sdesc) = 0;
+ virtual bool SetLocalContent_w(const MediaContentDescription* content,
+ ContentAction action) = 0;
+ virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action) = 0;
+
+ bool SetSrtp_w(const std::vector<CryptoParams>& params, ContentAction action,
+ ContentSource src);
+ bool SetRtcpMux_w(bool enable, ContentAction action, ContentSource src);
+
+ struct SetBandwidthData : public talk_base::MessageData {
+ explicit SetBandwidthData(int value) : value(value), result(false) {}
+ int value;
+ bool result;
+ };
+ bool SetMaxSendBandwidth_w(int max_bandwidth);
+
+ // From MessageHandler
+ virtual void OnMessage(talk_base::Message *pmsg);
+
+ // Handled in derived classes
+ virtual void OnConnectionMonitorUpdate(SocketMonitor *monitor,
+ const std::vector<ConnectionInfo> &infos) = 0;
+
+ private:
+ talk_base::Thread *worker_thread_;
+ MediaEngine *media_engine_;
+ BaseSession *session_;
+ MediaChannel *media_channel_;
+ // Media sinks to handle the received or sent RTP/RTCP packets. These are
+ // reference to the objects owned by the media recorder.
+ MediaSinkInterface* received_media_sink_;
+ MediaSinkInterface* sent_media_sink_;
+ talk_base::CriticalSection sink_critical_section_;
+
+ std::string content_name_;
+ TransportChannel *transport_channel_;
+ TransportChannel *rtcp_transport_channel_;
+ SrtpFilter srtp_filter_;
+ RtcpMuxFilter rtcp_mux_filter_;
+ talk_base::scoped_ptr<SocketMonitor> socket_monitor_;
+ bool enabled_;
+ bool writable_;
+ bool has_codec_;
+ bool muted_;
+};
+
+// VoiceChannel is a specialization that adds support for early media, DTMF,
+// and input/output level monitoring.
+class VoiceChannel : public BaseChannel {
+ public:
+ VoiceChannel(talk_base::Thread *thread, MediaEngine *media_engine,
+ VoiceMediaChannel *channel, BaseSession *session,
+ const std::string& content_name, bool rtcp);
+ ~VoiceChannel();
+
+ // downcasts a MediaChannel
+ virtual VoiceMediaChannel* media_channel() const {
+ return static_cast<VoiceMediaChannel*>(BaseChannel::media_channel());
+ }
+
+ // Add an incoming stream with the specified SSRC.
+ bool AddStream(uint32 ssrc);
+
+ bool SetRingbackTone(const void* buf, int len);
+ void SetEarlyMedia(bool enable);
+ // This signal is emitted when we have gone a period of time without
+ // receiving early media. When received, a UI should start playing its
+ // own ringing sound
+ sigslot::signal1<VoiceChannel*> SignalEarlyMediaTimeout;
+
+ bool PlayRingbackTone(bool play, bool loop);
+ bool PressDTMF(int digit, bool playout);
+
+ // Monitoring functions
+ sigslot::signal2<VoiceChannel*, const std::vector<ConnectionInfo> &>
+ SignalConnectionMonitor;
+
+ void StartMediaMonitor(int cms);
+ void StopMediaMonitor();
+ sigslot::signal2<VoiceChannel*, const VoiceMediaInfo&> SignalMediaMonitor;
+
+ void StartAudioMonitor(int cms);
+ void StopAudioMonitor();
+ sigslot::signal2<VoiceChannel*, const AudioInfo&> SignalAudioMonitor;
+
+ int GetInputLevel_w();
+ int GetOutputLevel_w();
+ void GetActiveStreams_w(AudioInfo::StreamList* actives);
+
+ // Signal errors from VoiceMediaChannel. Arguments are:
+ // ssrc(uint32), and error(VoiceMediaChannel::Error).
+ sigslot::signal3<VoiceChannel*, uint32, VoiceMediaChannel::Error>
+ SignalMediaError;
+
+ private:
+ struct SetRingbackToneMessageData : public talk_base::MessageData {
+ SetRingbackToneMessageData(const void* b, int l)
+ : buf(b),
+ len(l) {
+ }
+ const void* buf;
+ int len;
+ };
+ struct PlayRingbackToneMessageData : public talk_base::MessageData {
+ PlayRingbackToneMessageData(bool p, bool l)
+ : play(p),
+ loop(l),
+ result(false) {
+ }
+ bool play;
+ bool loop;
+ bool result;
+ };
+ struct DtmfMessageData : public talk_base::MessageData {
+ DtmfMessageData(int d, bool p)
+ : digit(d),
+ playout(p),
+ result(false) {
+ }
+ int digit;
+ bool playout;
+ bool result;
+ };
+
+ // overrides from BaseChannel
+ virtual void OnChannelRead(TransportChannel* channel,
+ const char *data, size_t len);
+ virtual void ChangeState();
+ virtual const MediaContentDescription* GetFirstContent(
+ const SessionDescription* sdesc);
+ virtual bool SetLocalContent_w(const MediaContentDescription* content,
+ ContentAction action);
+ virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action);
+
+ void AddStream_w(uint32 ssrc);
+ void RemoveStream_w(uint32 ssrc);
+
+ void SetRingbackTone_w(const void* buf, int len);
+ bool PlayRingbackTone_w(bool play, bool loop);
+ void HandleEarlyMediaTimeout();
+ bool PressDTMF_w(int digit, bool playout);
+
+ virtual void OnMessage(talk_base::Message *pmsg);
+ virtual void OnConnectionMonitorUpdate(
+ SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
+ virtual void OnMediaMonitorUpdate(
+ VoiceMediaChannel *media_channel, const VoiceMediaInfo& info);
+ void OnAudioMonitorUpdate(AudioMonitor *monitor, const AudioInfo& info);
+ void OnVoiceChannelError(uint32 ssrc, VoiceMediaChannel::Error error);
+ void SendLastMediaError();
+
+ static const int kEarlyMediaTimeout = 1000;
+ bool received_media_;
+ talk_base::scoped_ptr<VoiceMediaMonitor> media_monitor_;
+ talk_base::scoped_ptr<AudioMonitor> audio_monitor_;
+};
+
+// VideoChannel is a specialization for video.
+class VideoChannel : public BaseChannel {
+ public:
+ VideoChannel(talk_base::Thread *thread, MediaEngine *media_engine,
+ VideoMediaChannel *channel, BaseSession *session,
+ const std::string& content_name, bool rtcp,
+ VoiceChannel *voice_channel);
+ ~VideoChannel();
+
+ // downcasts a MediaChannel
+ virtual VideoMediaChannel* media_channel() const {
+ return static_cast<VideoMediaChannel*>(BaseChannel::media_channel());
+ }
+
+ // Add an incoming stream with the specified SSRC.
+ bool AddStream(uint32 ssrc, uint32 voice_ssrc);
+
+ bool SetRenderer(uint32 ssrc, VideoRenderer* renderer);
+
+
+ sigslot::signal2<VideoChannel*, const std::vector<ConnectionInfo> &>
+ SignalConnectionMonitor;
+
+ void StartMediaMonitor(int cms);
+ void StopMediaMonitor();
+ sigslot::signal2<VideoChannel*, const VideoMediaInfo&> SignalMediaMonitor;
+
+ bool SendIntraFrame();
+ bool RequestIntraFrame();
+
+ sigslot::signal3<VideoChannel*, uint32, VideoMediaChannel::Error>
+ SignalMediaError;
+
+ private:
+ // overrides from BaseChannel
+ virtual void ChangeState();
+ virtual const MediaContentDescription* GetFirstContent(
+ const SessionDescription* sdesc);
+ virtual bool SetLocalContent_w(const MediaContentDescription* content,
+ ContentAction action);
+ virtual bool SetRemoteContent_w(const MediaContentDescription* content,
+ ContentAction action);
+
+ void AddStream_w(uint32 ssrc, uint32 voice_ssrc);
+ void RemoveStream_w(uint32 ssrc);
+
+ void SendIntraFrame_w() {
+ media_channel()->SendIntraFrame();
+ }
+ void RequestIntraFrame_w() {
+ media_channel()->RequestIntraFrame();
+ }
+
+ struct RenderMessageData : public talk_base::MessageData {
+ RenderMessageData(uint32 s, VideoRenderer* r) : ssrc(s), renderer(r) {}
+ uint32 ssrc;
+ VideoRenderer* renderer;
+ };
+
+
+ void SetRenderer_w(uint32 ssrc, VideoRenderer* renderer);
+
+
+ virtual void OnMessage(talk_base::Message *pmsg);
+ virtual void OnConnectionMonitorUpdate(
+ SocketMonitor *monitor, const std::vector<ConnectionInfo> &infos);
+ virtual void OnMediaMonitorUpdate(
+ VideoMediaChannel *media_channel, const VideoMediaInfo& info);
+ void OnVideoChannelError(uint32 ssrc, VideoMediaChannel::Error error);
+
+ VoiceChannel *voice_channel_;
+ VideoRenderer *renderer_;
+ talk_base::scoped_ptr<VideoMediaMonitor> media_monitor_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_CHANNEL_H_
diff --git a/talk/session/phone/channelmanager.cc b/talk/session/phone/channelmanager.cc
new file mode 100644
index 0000000..d8c97cd
--- /dev/null
+++ b/talk/session/phone/channelmanager.cc
@@ -0,0 +1,769 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/channelmanager.h"
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <algorithm>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/stringencode.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/soundclip.h"
+
+namespace cricket {
+
+enum {
+ MSG_CREATEVOICECHANNEL = 1,
+ MSG_DESTROYVOICECHANNEL = 2,
+ MSG_SETAUDIOOPTIONS = 3,
+ MSG_SETOUTPUTVOLUME = 4,
+ MSG_SETLOCALMONITOR = 5,
+ MSG_SETVOICELOGGING = 6,
+ MSG_CREATEVIDEOCHANNEL = 11,
+ MSG_DESTROYVIDEOCHANNEL = 12,
+ MSG_SETVIDEOOPTIONS = 13,
+ MSG_SETLOCALRENDERER = 14,
+ MSG_SETDEFAULTVIDEOENCODERCONFIG = 15,
+ MSG_SETVIDEOLOGGING = 16,
+ MSG_CREATESOUNDCLIP = 17,
+ MSG_DESTROYSOUNDCLIP = 18,
+ MSG_CAMERASTARTED = 19,
+ MSG_SETVIDEOCAPTURE = 20,
+};
+
+struct CreationParams : public talk_base::MessageData {
+ CreationParams(BaseSession* session, const std::string& content_name,
+ bool rtcp, VoiceChannel* voice_channel)
+ : session(session),
+ content_name(content_name),
+ rtcp(rtcp),
+ voice_channel(voice_channel),
+ video_channel(NULL) {}
+ BaseSession* session;
+ std::string content_name;
+ bool rtcp;
+ VoiceChannel* voice_channel;
+ VideoChannel* video_channel;
+};
+
+struct AudioOptions : public talk_base::MessageData {
+ AudioOptions(int o, const Device* in, const Device* out)
+ : options(o), in_device(in), out_device(out) {}
+ int options;
+ const Device* in_device;
+ const Device* out_device;
+ bool result;
+};
+
+struct VolumeLevel : public talk_base::MessageData {
+ explicit VolumeLevel(int l) : level(l), result(false) {}
+ int level;
+ bool result;
+};
+
+struct VideoOptions : public talk_base::MessageData {
+ explicit VideoOptions(const Device* d) : cam_device(d), result(false) {}
+ const Device* cam_device;
+ bool result;
+};
+
+struct DefaultVideoEncoderConfig : public talk_base::MessageData {
+ explicit DefaultVideoEncoderConfig(const VideoEncoderConfig& c)
+ : config(c), result(false) {}
+ VideoEncoderConfig config;
+ bool result;
+};
+
+struct LocalMonitor : public talk_base::MessageData {
+ explicit LocalMonitor(bool e) : enable(e), result(false) {}
+ bool enable;
+ bool result;
+};
+
+struct LocalRenderer : public talk_base::MessageData {
+ explicit LocalRenderer(VideoRenderer* r) : renderer(r), result(false) {}
+ VideoRenderer* renderer;
+ bool result;
+};
+
+struct LoggingOptions : public talk_base::MessageData {
+ explicit LoggingOptions(int lev, const char* f) : level(lev), filter(f) {}
+ int level;
+ std::string filter;
+};
+
+struct CaptureParams : public talk_base::MessageData {
+ explicit CaptureParams(bool c) : capture(c), result(CR_FAILURE) {}
+
+ bool capture;
+ CaptureResult result;
+};
+
+ChannelManager::ChannelManager(talk_base::Thread* worker_thread)
+ : media_engine_(MediaEngine::Create()),
+ device_manager_(new DeviceManager()),
+ initialized_(false),
+ main_thread_(talk_base::Thread::Current()),
+ worker_thread_(worker_thread),
+ audio_in_device_(DeviceManager::kDefaultDeviceName),
+ audio_out_device_(DeviceManager::kDefaultDeviceName),
+ audio_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS),
+ local_renderer_(NULL),
+ capturing_(false),
+ monitoring_(false) {
+ Construct();
+}
+
+ChannelManager::ChannelManager(MediaEngine* me, DeviceManager* 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_options_(MediaEngine::DEFAULT_AUDIO_OPTIONS),
+ local_renderer_(NULL),
+ capturing_(false),
+ monitoring_(false) {
+ Construct();
+}
+
+void ChannelManager::Construct() {
+ // Init the device manager immediately, and set up our default video device.
+ SignalDevicesChange.repeat(device_manager_->SignalDevicesChange);
+ device_manager_->Init();
+ // Set camera_device_ to the name of the default video capturer.
+ SetVideoOptions(DeviceManager::kDefaultDeviceName);
+
+ // Camera is started asynchronously, request callbacks when startup
+ // completes to be able to forward them to the rendering manager.
+ media_engine_->SignalVideoCaptureResult.connect(
+ this, &ChannelManager::OnVideoCaptureResult);
+}
+
+ChannelManager::~ChannelManager() {
+ if (initialized_)
+ Terminate();
+}
+
+int ChannelManager::GetCapabilities() {
+ return media_engine_->GetCapabilities() & device_manager_->GetCapabilities();
+}
+
+void ChannelManager::GetSupportedAudioCodecs(
+ std::vector<AudioCodec>* codecs) const {
+ codecs->clear();
+
+ for (std::vector<AudioCodec>::const_iterator it =
+ media_engine_->audio_codecs().begin();
+ it != media_engine_->audio_codecs().end(); ++it) {
+ codecs->push_back(*it);
+ }
+}
+
+void ChannelManager::GetSupportedVideoCodecs(
+ std::vector<VideoCodec>* codecs) const {
+ codecs->clear();
+
+ std::vector<VideoCodec>::const_iterator it;
+ for (it = media_engine_->video_codecs().begin();
+ it != media_engine_->video_codecs().end(); ++it) {
+ codecs->push_back(*it);
+ }
+}
+
+bool ChannelManager::Init() {
+ ASSERT(!initialized_);
+ if (initialized_) {
+ return false;
+ }
+
+ ASSERT(worker_thread_ != NULL);
+ if (worker_thread_ && worker_thread_->started()) {
+ if (media_engine_->Init()) {
+ initialized_ = true;
+
+ // Now that we're initialized, apply any stored preferences. A preferred
+ // device might have been unplugged. In this case, we fallback to the
+ // default device but keep the user preferences. The preferences are
+ // changed only when the Javascript FE changes them.
+ const std::string preferred_audio_in_device = audio_in_device_;
+ const std::string preferred_audio_out_device = audio_out_device_;
+ const std::string preferred_camera_device = camera_device_;
+ Device device;
+ 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;
+ }
+ 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;
+ }
+ if (!device_manager_->GetVideoCaptureDevice(camera_device_, &device)) {
+ LOG(LS_WARNING) << "The preferred camera '" << camera_device_
+ << "' is unavailable. Fall back to the default.";
+ camera_device_ = DeviceManager::kDefaultDeviceName;
+ }
+
+ if (!SetAudioOptions(audio_in_device_, audio_out_device_,
+ audio_options_)) {
+ LOG(LS_WARNING) << "Failed to SetAudioOptions with"
+ << " microphone: " << audio_in_device_
+ << " speaker: " << audio_out_device_
+ << " options: " << audio_options_;
+ }
+ if (!SetVideoOptions(camera_device_)) {
+ LOG(LS_WARNING) << "Failed to SetVideoOptions with camera: "
+ << camera_device_;
+ }
+
+ // Restore the user preferences.
+ audio_in_device_ = preferred_audio_in_device;
+ audio_out_device_ = preferred_audio_out_device;
+ camera_device_ = preferred_camera_device;
+
+ // Now apply the default video codec that has been set earlier.
+ if (default_video_encoder_config_.max_codec.id != 0) {
+ SetDefaultVideoEncoderConfig(default_video_encoder_config_);
+ }
+ // And the local renderer.
+ if (local_renderer_) {
+ SetLocalRenderer(local_renderer_);
+ }
+ }
+ }
+ return initialized_;
+}
+
+void ChannelManager::Terminate() {
+ ASSERT(initialized_);
+ if (!initialized_) {
+ return;
+ }
+
+ // Need to destroy the voice/video channels
+ while (!video_channels_.empty()) {
+ DestroyVideoChannel_w(video_channels_.back());
+ }
+ while (!voice_channels_.empty()) {
+ DestroyVoiceChannel_w(voice_channels_.back());
+ }
+ while (!soundclips_.empty()) {
+ DestroySoundclip_w(soundclips_.back());
+ }
+
+ media_engine_->Terminate();
+ initialized_ = false;
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel(
+ BaseSession* session, const std::string& content_name, bool rtcp) {
+ CreationParams params(session, content_name, rtcp, NULL);
+ return (Send(MSG_CREATEVOICECHANNEL, ¶ms)) ? params.voice_channel : NULL;
+}
+
+VoiceChannel* ChannelManager::CreateVoiceChannel_w(
+ BaseSession* session, const std::string& content_name, bool rtcp) {
+ talk_base::CritScope cs(&crit_);
+
+ // This is ok to alloc from a thread other than the worker thread
+ ASSERT(initialized_);
+ VoiceMediaChannel* media_channel = media_engine_->CreateChannel();
+ if (media_channel == NULL)
+ return NULL;
+
+ VoiceChannel* voice_channel = new VoiceChannel(
+ worker_thread_, media_engine_.get(), media_channel,
+ session, content_name, rtcp);
+ voice_channels_.push_back(voice_channel);
+ return voice_channel;
+}
+
+void ChannelManager::DestroyVoiceChannel(VoiceChannel* voice_channel) {
+ if (voice_channel) {
+ talk_base::TypedMessageData<VoiceChannel *> data(voice_channel);
+ Send(MSG_DESTROYVOICECHANNEL, &data);
+ }
+}
+
+void ChannelManager::DestroyVoiceChannel_w(VoiceChannel* voice_channel) {
+ talk_base::CritScope cs(&crit_);
+ // Destroy voice channel.
+ ASSERT(initialized_);
+ VoiceChannels::iterator it = std::find(voice_channels_.begin(),
+ voice_channels_.end(), voice_channel);
+ ASSERT(it != voice_channels_.end());
+ if (it == voice_channels_.end())
+ return;
+
+ voice_channels_.erase(it);
+ delete voice_channel;
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel(
+ BaseSession* session, const std::string& content_name, bool rtcp,
+ VoiceChannel* voice_channel) {
+ CreationParams params(session, content_name, rtcp, voice_channel);
+ return (Send(MSG_CREATEVIDEOCHANNEL, ¶ms)) ? params.video_channel : NULL;
+}
+
+VideoChannel* ChannelManager::CreateVideoChannel_w(
+ BaseSession* session, const std::string& content_name, bool rtcp,
+ VoiceChannel* voice_channel) {
+ talk_base::CritScope cs(&crit_);
+
+ // This is ok to alloc from a thread other than the worker thread
+ ASSERT(initialized_);
+ VideoMediaChannel* media_channel =
+ // voice_channel can be NULL in case of NullVoiceEngine.
+ media_engine_->CreateVideoChannel(voice_channel ?
+ voice_channel->media_channel() : NULL);
+ if (media_channel == NULL)
+ return NULL;
+
+ VideoChannel* video_channel = new VideoChannel(
+ worker_thread_, media_engine_.get(), media_channel,
+ session, content_name, rtcp, voice_channel);
+ video_channels_.push_back(video_channel);
+ return video_channel;
+}
+
+void ChannelManager::DestroyVideoChannel(VideoChannel* video_channel) {
+ if (video_channel) {
+ talk_base::TypedMessageData<VideoChannel *> data(video_channel);
+ Send(MSG_DESTROYVIDEOCHANNEL, &data);
+ }
+}
+
+void ChannelManager::DestroyVideoChannel_w(VideoChannel *video_channel) {
+ talk_base::CritScope cs(&crit_);
+ // Destroy voice channel.
+ ASSERT(initialized_);
+ VideoChannels::iterator it = std::find(video_channels_.begin(),
+ video_channels_.end(), video_channel);
+ ASSERT(it != video_channels_.end());
+ if (it == video_channels_.end())
+ return;
+
+ video_channels_.erase(it);
+ delete video_channel;
+}
+
+Soundclip* ChannelManager::CreateSoundclip() {
+ talk_base::TypedMessageData<Soundclip*> data(NULL);
+ Send(MSG_CREATESOUNDCLIP, &data);
+ return data.data();
+}
+
+Soundclip* ChannelManager::CreateSoundclip_w() {
+ talk_base::CritScope cs(&crit_);
+
+ ASSERT(initialized_);
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+
+ SoundclipMedia* soundclip_media = media_engine_->CreateSoundclip();
+ if (!soundclip_media) {
+ return NULL;
+ }
+
+ Soundclip* soundclip = new Soundclip(worker_thread_, soundclip_media);
+ soundclips_.push_back(soundclip);
+ return soundclip;
+}
+
+void ChannelManager::DestroySoundclip(Soundclip* soundclip) {
+ if (soundclip) {
+ talk_base::TypedMessageData<Soundclip*> data(soundclip);
+ Send(MSG_DESTROYSOUNDCLIP, &data);
+ }
+}
+
+void ChannelManager::DestroySoundclip_w(Soundclip* soundclip) {
+ talk_base::CritScope cs(&crit_);
+ // Destroy soundclip.
+ ASSERT(initialized_);
+ Soundclips::iterator it = std::find(soundclips_.begin(),
+ soundclips_.end(), soundclip);
+ ASSERT(it != soundclips_.end());
+ if (it == soundclips_.end())
+ return;
+
+ soundclips_.erase(it);
+ delete soundclip;
+}
+
+bool ChannelManager::GetAudioOptions(std::string* in_name,
+ std::string* out_name, int* opts) {
+ *in_name = audio_in_device_;
+ *out_name = audio_out_device_;
+ *opts = audio_options_;
+ return true;
+}
+
+bool ChannelManager::SetAudioOptions(const std::string& in_name,
+ const std::string& out_name, int opts) {
+ // Get device ids from DeviceManager.
+ Device in_dev, out_dev;
+ if (!device_manager_->GetAudioInputDevice(in_name, &in_dev) ||
+ !device_manager_->GetAudioOutputDevice(out_name, &out_dev)) {
+ LOG(LS_WARNING) << "Device manager can't find selected device";
+ return false;
+ }
+
+ // If we're initialized, pass the settings to the media engine.
+ bool ret = true;
+ if (initialized_) {
+ AudioOptions options(opts, &in_dev, &out_dev);
+ ret = (Send(MSG_SETAUDIOOPTIONS, &options) && options.result);
+ }
+
+ // If all worked well, save the values for use in GetAudioOptions.
+ if (ret) {
+ audio_options_ = opts;
+ audio_in_device_ = in_name;
+ audio_out_device_ = out_name;
+ }
+ return ret;
+}
+
+bool ChannelManager::SetAudioOptions_w(int opts, const Device* in_dev,
+ const Device* out_dev) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+
+ // Set audio options
+ bool ret = media_engine_->SetAudioOptions(opts);
+
+ // Set the audio devices
+ if (ret) {
+ talk_base::CritScope cs(&crit_);
+ ret = media_engine_->SetSoundDevices(in_dev, out_dev);
+ }
+
+ return ret;
+}
+
+bool ChannelManager::SetOutputVolume(int level) {
+ VolumeLevel volume(level);
+ return (Send(MSG_SETOUTPUTVOLUME, &volume) && volume.result);
+}
+
+bool ChannelManager::SetOutputVolume_w(int level) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetOutputVolume(level);
+}
+
+bool ChannelManager::GetVideoOptions(std::string* cam_name) {
+ *cam_name = camera_device_;
+ return true;
+}
+
+bool ChannelManager::SetVideoOptions(const std::string& cam_name) {
+ Device device;
+ if (!device_manager_->GetVideoCaptureDevice(cam_name, &device)) {
+ LOG(LS_WARNING) << "Device manager can't find camera: " << cam_name;
+ return false;
+ }
+
+ // If we're running, tell the media engine about it.
+ bool ret = true;
+ if (initialized_) {
+ VideoOptions options(&device);
+ ret = (Send(MSG_SETVIDEOOPTIONS, &options) && options.result);
+ }
+
+ // If everything worked, retain the name of the selected camera.
+ if (ret) {
+ camera_device_ = device.name;
+ }
+ return ret;
+}
+
+bool ChannelManager::SetVideoOptions_w(const Device* cam_device) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+
+ // Set the video input device
+ return media_engine_->SetVideoCaptureDevice(cam_device);
+}
+
+bool ChannelManager::SetDefaultVideoEncoderConfig(const VideoEncoderConfig& c) {
+ bool ret = true;
+ if (initialized_) {
+ DefaultVideoEncoderConfig config(c);
+ ret = Send(MSG_SETDEFAULTVIDEOENCODERCONFIG, &config) && config.result;
+ }
+ if (ret) {
+ default_video_encoder_config_ = c;
+ }
+ return ret;
+}
+
+bool ChannelManager::SetDefaultVideoEncoderConfig_w(
+ const VideoEncoderConfig& c) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetDefaultVideoEncoderConfig(c);
+}
+
+bool ChannelManager::SetLocalMonitor(bool enable) {
+ LocalMonitor monitor(enable);
+ bool ret = Send(MSG_SETLOCALMONITOR, &monitor) && monitor.result;
+ if (ret) {
+ monitoring_ = enable;
+ }
+ return ret;
+}
+
+bool ChannelManager::SetLocalMonitor_w(bool enable) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetLocalMonitor(enable);
+}
+
+bool ChannelManager::SetLocalRenderer(VideoRenderer* renderer) {
+ bool ret = true;
+ if (initialized_) {
+ LocalRenderer local(renderer);
+ ret = (Send(MSG_SETLOCALRENDERER, &local) && local.result);
+ }
+ if (ret) {
+ local_renderer_ = renderer;
+ }
+ return ret;
+}
+
+bool ChannelManager::SetLocalRenderer_w(VideoRenderer* renderer) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetLocalRenderer(renderer);
+}
+
+CaptureResult ChannelManager::SetVideoCapture(bool capture) {
+ bool ret;
+ CaptureParams capture_params(capture);
+ ret = (Send(MSG_SETVIDEOCAPTURE, &capture_params) &&
+ (capture_params.result != CR_FAILURE));
+ if (ret) {
+ capturing_ = capture;
+ }
+ return capture_params.result;
+}
+
+CaptureResult ChannelManager::SetVideoCapture_w(bool capture) {
+ ASSERT(worker_thread_ == talk_base::Thread::Current());
+ ASSERT(initialized_);
+ return media_engine_->SetVideoCapture(capture);
+}
+
+void ChannelManager::SetVoiceLogging(int level, const char* filter) {
+ SetMediaLogging(false, level, filter);
+}
+
+void ChannelManager::SetVideoLogging(int level, const char* filter) {
+ SetMediaLogging(true, level, filter);
+}
+
+void ChannelManager::SetMediaLogging(bool video, int level,
+ const char* filter) {
+ // Can be called before initialization; in this case, the worker function
+ // is simply called on the main thread.
+ if (initialized_) {
+ LoggingOptions options(level, filter);
+ Send((video) ? MSG_SETVIDEOLOGGING : MSG_SETVOICELOGGING, &options);
+ } else {
+ SetMediaLogging_w(video, level, filter);
+ }
+}
+
+void ChannelManager::SetMediaLogging_w(bool video, int level,
+ const char* filter) {
+ // Can be called before initialization
+ ASSERT(worker_thread_ == talk_base::Thread::Current() || !initialized_);
+ if (video) {
+ media_engine_->SetVideoLogging(level, filter);
+ } else {
+ media_engine_->SetVoiceLogging(level, filter);
+ }
+}
+
+bool ChannelManager::Send(uint32 id, talk_base::MessageData* data) {
+ if (!worker_thread_ || !initialized_) return false;
+ worker_thread_->Send(this, id, data);
+ return true;
+}
+
+void ChannelManager::OnVideoCaptureResult(CaptureResult result) {
+ capturing_ = result == CR_SUCCESS;
+ main_thread_->Post(this, MSG_CAMERASTARTED,
+ new talk_base::TypedMessageData<CaptureResult>(result));
+}
+
+void ChannelManager::OnMessage(talk_base::Message* message) {
+ talk_base::MessageData* data = message->pdata;
+ switch (message->message_id) {
+ case MSG_CREATEVOICECHANNEL: {
+ CreationParams* p = static_cast<CreationParams*>(data);
+ p->voice_channel =
+ CreateVoiceChannel_w(p->session, p->content_name, p->rtcp);
+ break;
+ }
+ case MSG_DESTROYVOICECHANNEL: {
+ VoiceChannel* p = static_cast<talk_base::TypedMessageData<VoiceChannel*>*>
+ (data)->data();
+ DestroyVoiceChannel_w(p);
+ break;
+ }
+ case MSG_CREATEVIDEOCHANNEL: {
+ CreationParams* p = static_cast<CreationParams*>(data);
+ p->video_channel = CreateVideoChannel_w(p->session, p->content_name,
+ p->rtcp, p->voice_channel);
+ break;
+ }
+ case MSG_DESTROYVIDEOCHANNEL: {
+ VideoChannel* p = static_cast<talk_base::TypedMessageData<VideoChannel*>*>
+ (data)->data();
+ DestroyVideoChannel_w(p);
+ break;
+ }
+ case MSG_CREATESOUNDCLIP: {
+ talk_base::TypedMessageData<Soundclip*> *p =
+ static_cast<talk_base::TypedMessageData<Soundclip*>*>(data);
+ p->data() = CreateSoundclip_w();
+ break;
+ }
+ case MSG_DESTROYSOUNDCLIP: {
+ talk_base::TypedMessageData<Soundclip*> *p =
+ static_cast<talk_base::TypedMessageData<Soundclip*>*>(data);
+ DestroySoundclip_w(p->data());
+ break;
+ }
+ case MSG_SETAUDIOOPTIONS: {
+ AudioOptions* p = static_cast<AudioOptions*>(data);
+ p->result = SetAudioOptions_w(p->options,
+ p->in_device, p->out_device);
+ break;
+ }
+ case MSG_SETOUTPUTVOLUME: {
+ VolumeLevel* p = static_cast<VolumeLevel*>(data);
+ p->result = SetOutputVolume_w(p->level);
+ break;
+ }
+ case MSG_SETLOCALMONITOR: {
+ LocalMonitor* p = static_cast<LocalMonitor*>(data);
+ p->result = SetLocalMonitor_w(p->enable);
+ break;
+ }
+ case MSG_SETVIDEOOPTIONS: {
+ VideoOptions* p = static_cast<VideoOptions*>(data);
+ p->result = SetVideoOptions_w(p->cam_device);
+ break;
+ }
+ case MSG_SETDEFAULTVIDEOENCODERCONFIG: {
+ DefaultVideoEncoderConfig* p =
+ static_cast<DefaultVideoEncoderConfig*>(data);
+ p->result = SetDefaultVideoEncoderConfig_w(p->config);
+ break;
+ }
+ case MSG_SETLOCALRENDERER: {
+ LocalRenderer* p = static_cast<LocalRenderer*>(data);
+ p->result = SetLocalRenderer_w(p->renderer);
+ break;
+ }
+ case MSG_SETVIDEOCAPTURE: {
+ CaptureParams* p = static_cast<CaptureParams*>(data);
+ p->result = SetVideoCapture_w(p->capture);
+ break;
+ }
+ case MSG_SETVOICELOGGING:
+ case MSG_SETVIDEOLOGGING: {
+ LoggingOptions* p = static_cast<LoggingOptions*>(data);
+ bool video = (message->message_id == MSG_SETVIDEOLOGGING);
+ SetMediaLogging_w(video, p->level, p->filter.c_str());
+ break;
+ }
+ case MSG_CAMERASTARTED: {
+ talk_base::TypedMessageData<CaptureResult>* data =
+ static_cast<talk_base::TypedMessageData<CaptureResult>*>(
+ message->pdata);
+ SignalVideoCaptureResult(data->data());
+ delete data;
+ break;
+ }
+ }
+}
+
+static void GetDeviceNames(const std::vector<Device>& devs,
+ std::vector<std::string>* names) {
+ names->clear();
+ for (size_t i = 0; i < devs.size(); ++i) {
+ names->push_back(devs[i].name);
+ }
+}
+
+bool ChannelManager::GetAudioInputDevices(std::vector<std::string>* names) {
+ names->clear();
+ std::vector<Device> devs;
+ bool ret = device_manager_->GetAudioInputDevices(&devs);
+ if (ret)
+ GetDeviceNames(devs, names);
+
+ return ret;
+}
+
+bool ChannelManager::GetAudioOutputDevices(std::vector<std::string>* names) {
+ names->clear();
+ std::vector<Device> devs;
+ bool ret = device_manager_->GetAudioOutputDevices(&devs);
+ if (ret)
+ GetDeviceNames(devs, names);
+
+ return ret;
+}
+
+bool ChannelManager::GetVideoCaptureDevices(std::vector<std::string>* names) {
+ names->clear();
+ std::vector<Device> devs;
+ bool ret = device_manager_->GetVideoCaptureDevices(&devs);
+ if (ret)
+ GetDeviceNames(devs, names);
+
+ return ret;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/channelmanager.h b/talk/session/phone/channelmanager.h
new file mode 100644
index 0000000..dfcfffe
--- /dev/null
+++ b/talk/session/phone/channelmanager.h
@@ -0,0 +1,212 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_CHANNELMANAGER_H_
+#define TALK_SESSION_PHONE_CHANNELMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/session.h"
+#include "talk/session/phone/voicechannel.h"
+#include "talk/session/phone/mediaengine.h"
+#include "talk/session/phone/devicemanager.h"
+
+namespace cricket {
+
+class Soundclip;
+class VoiceChannel;
+
+// ChannelManager allows the MediaEngine to run on a separate thread, and takes
+// care of marshalling calls between threads. It also creates and keeps track of
+// voice and video channels; by doing so, it can temporarily pause all the
+// channels when a new audio or video device is chosen. The voice and video
+// channels are stored in separate vectors, to easily allow operations on just
+// voice or just video channels.
+// ChannelManager also allows the application to discover what devices it has
+// using device manager.
+class ChannelManager : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ // Creates the channel manager, and specifies the worker thread to use.
+ 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(MediaEngine* me, DeviceManager* dm, talk_base::Thread* worker);
+ ~ChannelManager();
+
+ // Accessors for the worker thread, allowing it to be set after construction,
+ // but before Init. set_worker_thread will return false if called after Init.
+ talk_base::Thread* worker_thread() const { return worker_thread_; }
+ bool set_worker_thread(talk_base::Thread* thread) {
+ if (initialized_) return false;
+ worker_thread_ = thread;
+ return true;
+ }
+
+ // Gets capabilities. Can be called prior to starting the media engine.
+ int GetCapabilities();
+
+ // Retrieves the list of supported audio & video codec types.
+ // Can be called before starting the media engine.
+ void GetSupportedAudioCodecs(std::vector<AudioCodec>* codecs) const;
+ void GetSupportedVideoCodecs(std::vector<VideoCodec>* codecs) const;
+
+ // Determines if a specific audio or video codec is supported.
+ // Can be called before starting the media engine.
+ bool FindAudioCodec(const AudioCodec& codec) const {
+ return media_engine_->FindAudioCodec(codec);
+ }
+ bool FindVideoCodec(const VideoCodec& video_codec) const {
+ return media_engine_->FindVideoCodec(video_codec);
+ }
+
+ // Indicates whether the media engine is started.
+ bool initialized() const { return initialized_; }
+ // Starts up the media engine.
+ bool Init();
+ // TODO: Remove this temporary API once Flute is updated.
+ bool Init(talk_base::Thread* thread) {
+ return set_worker_thread(thread) && Init();
+ }
+ // Shuts down the media engine.
+ void Terminate();
+
+ // The operations below all occur on the worker thread.
+
+ // Creates a voice channel, to be associated with the specified session.
+ VoiceChannel* CreateVoiceChannel(
+ BaseSession* session, const std::string& content_name, bool rtcp);
+ // Destroys a voice channel created with the Create API.
+ void DestroyVoiceChannel(VoiceChannel* voice_channel);
+ // Creates a video channel, synced with the specified voice channel, and
+ // associated with the specified session.
+ VideoChannel* CreateVideoChannel(
+ BaseSession* session, const std::string& content_name, bool rtcp,
+ VoiceChannel* voice_channel);
+ // Destroys a video channel created with the Create API.
+ void DestroyVideoChannel(VideoChannel* video_channel);
+
+ // Creates a soundclip.
+ Soundclip* CreateSoundclip();
+ // Destroys a soundclip created with the Create API.
+ void DestroySoundclip(Soundclip* soundclip);
+
+ // Indicates whether any channels exist.
+ bool has_channels() const {
+ return (!voice_channels_.empty() || !video_channels_.empty() ||
+ !soundclips_.empty());
+ }
+
+ // Configures the audio and video devices.
+ bool GetAudioOptions(std::string* wave_in_device,
+ std::string* wave_out_device, int* opts);
+ bool SetAudioOptions(const std::string& wave_in_device,
+ const std::string& wave_out_device, int opts);
+ bool SetOutputVolume(int level);
+ bool GetVideoOptions(std::string* cam_device);
+ bool SetVideoOptions(const std::string& cam_device);
+ bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config);
+
+ // Starts/stops the local microphone and enables polling of the input level.
+ bool SetLocalMonitor(bool enable);
+ bool monitoring() const { return monitoring_; }
+ // Sets the local renderer where to renderer the local camera.
+ bool SetLocalRenderer(VideoRenderer* renderer);
+ // Starts and stops the local camera and renders it to the local renderer.
+ CaptureResult SetVideoCapture(bool capture);
+ bool capturing() const { return capturing_; }
+
+ // Configures the logging output of the mediaengine(s).
+ void SetVoiceLogging(int level, const char* filter);
+ void SetVideoLogging(int level, const char* filter);
+
+ // The operations below occur on the main thread.
+
+ bool GetAudioInputDevices(std::vector<std::string>* names);
+ bool GetAudioOutputDevices(std::vector<std::string>* names);
+ bool GetVideoCaptureDevices(std::vector<std::string>* names);
+ sigslot::repeater0<> SignalDevicesChange;
+ sigslot::signal1<CaptureResult> SignalVideoCaptureResult;
+
+ private:
+ typedef std::vector<VoiceChannel*> VoiceChannels;
+ typedef std::vector<VideoChannel*> VideoChannels;
+ typedef std::vector<Soundclip*> Soundclips;
+
+ void Construct();
+ bool Send(uint32 id, talk_base::MessageData* pdata);
+ VoiceChannel* CreateVoiceChannel_w(
+ BaseSession* session, const std::string& content_name, bool rtcp);
+ void DestroyVoiceChannel_w(VoiceChannel* voice_channel);
+ VideoChannel* CreateVideoChannel_w(
+ BaseSession* session, const std::string& content_name, bool rtcp,
+ VoiceChannel* voice_channel);
+ void DestroyVideoChannel_w(VideoChannel* video_channel);
+ Soundclip* CreateSoundclip_w();
+ void DestroySoundclip_w(Soundclip* soundclip);
+ bool SetAudioOptions_w(int opts, const Device* in_dev,
+ const Device* out_dev);
+ bool SetOutputVolume_w(int level);
+ bool SetLocalMonitor_w(bool enable);
+ bool SetVideoOptions_w(const Device* cam_device);
+ bool SetDefaultVideoEncoderConfig_w(const VideoEncoderConfig& config);
+ bool SetLocalRenderer_w(VideoRenderer* renderer);
+ CaptureResult SetVideoCapture_w(bool capture);
+ void SetMediaLogging(bool video, int level, const char* filter);
+ void SetMediaLogging_w(bool video, int level, const char* filter);
+ void OnVideoCaptureResult(CaptureResult result);
+ void OnMessage(talk_base::Message *message);
+
+ talk_base::CriticalSection crit_;
+ talk_base::scoped_ptr<MediaEngine> media_engine_;
+ talk_base::scoped_ptr<DeviceManager> device_manager_;
+ bool initialized_;
+ talk_base::Thread* main_thread_;
+ talk_base::Thread* worker_thread_;
+
+ VoiceChannels voice_channels_;
+ VideoChannels video_channels_;
+ Soundclips soundclips_;
+
+ std::string audio_in_device_;
+ std::string audio_out_device_;
+ int audio_options_;
+ std::string camera_device_;
+ VideoEncoderConfig default_video_encoder_config_;
+ VideoRenderer* local_renderer_;
+
+ bool capturing_;
+ bool monitoring_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_CHANNELMANAGER_H_
diff --git a/talk/session/phone/codec.cc b/talk/session/phone/codec.cc
new file mode 100644
index 0000000..01d1a7b
--- /dev/null
+++ b/talk/session/phone/codec.cc
@@ -0,0 +1,78 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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/session/phone/codec.h"
+#include <sstream>
+
+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);
+}
+
+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.
+ // 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.channels == 0 || channels == codec.channels);
+}
+
+std::string AudioCodec::ToString() const {
+ std::ostringstream os;
+ os << "AudioCodec[" << id << ":" << name << ":" << clockrate << ":" << bitrate
+ << ":" << channels << ":" << preference << "]";
+ return os.str();
+}
+
+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);
+}
+
+bool VideoCodec::Matches(const VideoCodec& codec) const {
+ // Only the id and name are matched.
+ return Matches(codec.id, codec.name);
+}
+
+std::string VideoCodec::ToString() const {
+ std::ostringstream os;
+ os << "VideoCodec[" << id << ":" << name << ":" << width << ":" << height
+ << ":" << framerate << ":" << preference << "]";
+ return os.str();
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/codec.h b/talk/session/phone/codec.h
new file mode 100644
index 0000000..8536dbb
--- /dev/null
+++ b/talk/session/phone/codec.h
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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_CODEC_H_
+#define TALK_SESSION_PHONE_CODEC_H_
+
+#include <string>
+
+namespace cricket {
+
+struct AudioCodec {
+ int id;
+ std::string name;
+ int clockrate;
+ int bitrate;
+ int channels;
+
+ int preference;
+
+ // Creates a codec with the given parameters.
+ AudioCodec(int pt, const std::string& nm, int cr, int br, int cs, int pr)
+ : id(pt), name(nm), clockrate(cr), bitrate(br),
+ channels(cs), preference(pr) {}
+
+ // Creates an empty codec.
+ AudioCodec() : id(0), clockrate(0), bitrate(0), channels(0), preference(0) {}
+
+ // Indicates if this codec is compatible with the specified codec.
+ bool Matches(int payload, const std::string& nm) const;
+ bool Matches(const AudioCodec& codec) const;
+
+ static bool Preferable(const AudioCodec& first, const AudioCodec& other) {
+ return first.preference > other.preference;
+ }
+
+ std::string ToString() const;
+
+ AudioCodec& operator=(const AudioCodec& c) {
+ this->id = c.id; // id is reserved in objective-c
+ name = c.name;
+ clockrate = c.clockrate;
+ bitrate = c.bitrate;
+ channels = c.channels;
+ preference = c.preference;
+ return *this;
+ }
+
+ bool operator==(const AudioCodec& c) const {
+ return this->id == c.id && // id is reserved in objective-c
+ name == c.name &&
+ clockrate == c.clockrate &&
+ bitrate == c.bitrate &&
+ channels == c.channels &&
+ preference == c.preference;
+ }
+
+ bool operator!=(const AudioCodec& c) const {
+ return !(*this == c);
+ }
+};
+
+struct VideoCodec {
+ int id;
+ std::string name;
+ int width;
+ int height;
+ int framerate;
+
+ int preference;
+
+ // Creates a codec with the given parameters.
+ VideoCodec(int pt, const std::string& nm, int w, int h, int fr, int pr)
+ : id(pt), name(nm), width(w), height(h), framerate(fr), preference(pr) {}
+
+ // Creates an empty codec.
+ VideoCodec()
+ : id(0), width(0), height(0), framerate(0), preference(0) {}
+
+ bool Matches(int payload, const std::string& nm) const;
+ bool Matches(const VideoCodec& codec) const;
+
+ static bool Preferable(const VideoCodec& first, const VideoCodec& other) {
+ return first.preference > other.preference;
+ }
+
+ std::string ToString() const;
+
+ VideoCodec& operator=(const VideoCodec& c) {
+ this->id = c.id; // id is reserved in objective-c
+ name = c.name;
+ width = c.width;
+ height = c.height;
+ framerate = c.framerate;
+ preference = c.preference;
+ return *this;
+ }
+
+ bool operator==(const VideoCodec& c) const {
+ return this->id == c.id && // id is reserved in objective-c
+ name == c.name &&
+ width == c.width &&
+ height == c.height &&
+ framerate == c.framerate &&
+ preference == c.preference;
+ }
+
+ bool operator!=(const VideoCodec& c) const {
+ return !(*this == c);
+ }
+};
+
+struct VideoEncoderConfig {
+ static const int kDefaultMaxThreads = -1;
+ static const int kDefaultCpuProfile = -1;
+
+ VideoEncoderConfig()
+ : max_codec(),
+ num_threads(kDefaultMaxThreads),
+ cpu_profile(kDefaultCpuProfile) {
+ }
+
+ VideoEncoderConfig(const VideoCodec& c)
+ : max_codec(c),
+ num_threads(kDefaultMaxThreads),
+ cpu_profile(kDefaultCpuProfile) {
+ }
+
+ VideoEncoderConfig(const VideoCodec& c, int t, int p)
+ : max_codec(c),
+ num_threads(t),
+ cpu_profile(p) {
+ }
+
+ VideoEncoderConfig& operator=(const VideoEncoderConfig& config) {
+ max_codec = config.max_codec;
+ num_threads = config.num_threads;
+ cpu_profile = config.cpu_profile;
+ return *this;
+ }
+
+ bool operator==(const VideoEncoderConfig& config) const {
+ return max_codec == config.max_codec &&
+ num_threads == config.num_threads &&
+ cpu_profile == config.cpu_profile;
+ }
+
+ bool operator!=(const VideoEncoderConfig& config) const {
+ return !(*this == config);
+ }
+
+ VideoCodec max_codec;
+ int num_threads;
+ int cpu_profile;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_CODEC_H_
diff --git a/talk/session/phone/cryptoparams.h b/talk/session/phone/cryptoparams.h
new file mode 100644
index 0000000..15e94a0
--- /dev/null
+++ b/talk/session/phone/cryptoparams.h
@@ -0,0 +1,54 @@
+/*
+ * 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_SESSION_PHONE_CRYPTOPARAMS_H_
+#define TALK_SESSION_PHONE_CRYPTOPARAMS_H_
+
+#include <string>
+
+namespace cricket {
+
+// Parameters for SRTP negotiation, as described in RFC 4568.
+struct CryptoParams {
+ CryptoParams() : tag(0) {}
+ CryptoParams(int t, const std::string& cs,
+ const std::string& kp, const std::string& sp)
+ : tag(t), cipher_suite(cs), key_params(kp), session_params(sp) {}
+
+ bool Matches(const CryptoParams& params) const {
+ return (tag == params.tag && cipher_suite == params.cipher_suite);
+ }
+
+ int tag;
+ std::string cipher_suite;
+ std::string key_params;
+ std::string session_params;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_CRYPTOPARAMS_H_
diff --git a/talk/session/phone/devicemanager.cc b/talk/session/phone/devicemanager.cc
new file mode 100644
index 0000000..cb97301
--- /dev/null
+++ b/talk/session/phone/devicemanager.cc
@@ -0,0 +1,989 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/session/phone/devicemanager.h"
+
+#if WIN32
+#include <atlbase.h>
+#include <dbt.h>
+#include <strmif.h> // must come before ks.h
+#include <ks.h>
+#include <ksmedia.h>
+#define INITGUID // For PKEY_AudioEndpoint_GUID
+#include <mmdeviceapi.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <uuids.h>
+#include "talk/base/win32.h" // ToUtf8
+#include "talk/base/win32window.h"
+#elif OSX
+#include <CoreAudio/CoreAudio.h>
+#include <QuickTime/QuickTime.h>
+#elif LINUX
+#include <libudev.h>
+#include <unistd.h>
+#include "talk/base/linux.h"
+#include "talk/base/fileutils.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/physicalsocketserver.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/libudevsymboltable.h"
+#include "talk/session/phone/v4llookup.h"
+#include "talk/sound/platformsoundsystem.h"
+#include "talk/sound/platformsoundsystemfactory.h"
+#include "talk/sound/sounddevicelocator.h"
+#include "talk/sound/soundsysteminterface.h"
+#endif
+
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace cricket {
+// Initialize to empty string.
+const std::string DeviceManager::kDefaultDeviceName;
+
+#ifdef WIN32
+class DeviceWatcher : public talk_base::Win32Window {
+ public:
+ explicit DeviceWatcher(DeviceManager* dm);
+ bool Start();
+ void Stop();
+
+ private:
+ HDEVNOTIFY Register(REFGUID guid);
+ void Unregister(HDEVNOTIFY notify);
+ virtual bool OnMessage(UINT msg, WPARAM wp, LPARAM lp, LRESULT& result);
+
+ DeviceManager* manager_;
+ HDEVNOTIFY audio_notify_;
+ HDEVNOTIFY video_notify_;
+};
+#elif defined(LINUX)
+class DeviceWatcher : private talk_base::Dispatcher {
+ public:
+ explicit DeviceWatcher(DeviceManager* dm);
+ bool Start();
+ void Stop();
+
+ private:
+ virtual uint32 GetRequestedEvents();
+ virtual void OnPreEvent(uint32 ff);
+ virtual void OnEvent(uint32 ff, int err);
+ virtual int GetDescriptor();
+ virtual bool IsDescriptorClosed();
+
+ DeviceManager* manager_;
+ LibUDevSymbolTable libudev_;
+ struct udev* udev_;
+ struct udev_monitor* udev_monitor_;
+ bool registered_;
+};
+#define LATE(sym) LATESYM_GET(LibUDevSymbolTable, &libudev_, sym)
+#elif defined(OSX)
+class DeviceWatcher {
+ public:
+ explicit DeviceWatcher(DeviceManager* dm);
+ bool Start();
+ void Stop();
+ private:
+ DeviceManager* manager_;
+ void* impl_;
+};
+#elif defined(IOS) || defined(ANDROID)
+// We don't use DeviceWatcher on iOS or Android, so just stub out a noop class.
+class DeviceWatcher {
+ public:
+ explicit DeviceWatcher(DeviceManager* dm) {}
+ bool Start() { return true; }
+ void Stop() {}
+};
+#endif
+
+#if !defined(LINUX) && !defined(IOS)
+static bool ShouldDeviceBeIgnored(const std::string& device_name);
+#endif
+#ifndef OSX
+static bool GetVideoDevices(std::vector<Device>* out);
+#endif
+#if WIN32
+static const wchar_t kFriendlyName[] = L"FriendlyName";
+static const wchar_t kDevicePath[] = L"DevicePath";
+static const char kUsbDevicePathPrefix[] = "\\\\?\\usb";
+static bool GetDevices(const CLSID& catid, std::vector<Device>* out);
+static bool GetCoreAudioDevices(bool input, std::vector<Device>* devs);
+static bool GetWaveDevices(bool input, std::vector<Device>* devs);
+#elif OSX
+static const int kVideoDeviceOpenAttempts = 3;
+static const UInt32 kAudioDeviceNameLength = 64;
+// Obj-C functions defined in devicemanager-mac.mm
+extern void* CreateDeviceWatcherCallback(DeviceManager* dm);
+extern void ReleaseDeviceWatcherCallback(void* impl);
+extern bool GetQTKitVideoDevices(std::vector<Device>* out);
+static bool GetAudioDeviceIDs(bool inputs, std::vector<AudioDeviceID>* out);
+static bool GetAudioDeviceName(AudioDeviceID id, bool input, std::string* out);
+#endif
+
+DeviceManager::DeviceManager()
+ : initialized_(false),
+#if defined(WIN32)
+ need_couninitialize_(false),
+#endif
+ watcher_(new DeviceWatcher(this))
+#ifdef LINUX
+ , sound_system_(new PlatformSoundSystemFactory())
+#endif
+ {
+}
+
+DeviceManager::~DeviceManager() {
+ if (initialized_) {
+ Terminate();
+ }
+ delete watcher_;
+}
+
+bool DeviceManager::Init() {
+ if (!initialized_) {
+#if defined(WIN32)
+ HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ need_couninitialize_ = SUCCEEDED(hr);
+ if (FAILED(hr)) {
+ LOG(LS_ERROR) << "CoInitialize failed, hr=" << hr;
+ if (hr != RPC_E_CHANGED_MODE) {
+ return false;
+ }
+ }
+#endif
+ if (!watcher_->Start()) {
+ return false;
+ }
+ initialized_ = true;
+ }
+ return true;
+}
+
+void DeviceManager::Terminate() {
+ if (initialized_) {
+ watcher_->Stop();
+#if defined(WIN32)
+ if (need_couninitialize_) {
+ CoUninitialize();
+ need_couninitialize_ = false;
+ }
+#endif
+ initialized_ = false;
+ }
+}
+
+int DeviceManager::GetCapabilities() {
+ std::vector<Device> devices;
+ int caps = MediaEngine::VIDEO_RECV;
+ if (GetAudioInputDevices(&devices) && !devices.empty()) {
+ caps |= MediaEngine::AUDIO_SEND;
+ }
+ if (GetAudioOutputDevices(&devices) && !devices.empty()) {
+ caps |= MediaEngine::AUDIO_RECV;
+ }
+ if (GetVideoCaptureDevices(&devices) && !devices.empty()) {
+ caps |= MediaEngine::VIDEO_SEND;
+ }
+ return caps;
+}
+
+bool DeviceManager::GetAudioInputDevices(std::vector<Device>* devices) {
+ return GetAudioDevicesByPlatform(true, devices);
+}
+
+bool DeviceManager::GetAudioOutputDevices(std::vector<Device>* devices) {
+ return GetAudioDevicesByPlatform(false, devices);
+}
+
+bool DeviceManager::GetAudioInputDevice(const std::string& name, Device* out) {
+ return GetAudioDevice(true, name, out);
+}
+
+bool DeviceManager::GetAudioOutputDevice(const std::string& name, Device* out) {
+ return GetAudioDevice(false, name, out);
+}
+
+#ifdef OSX
+static bool FilterDevice(const Device& d) {
+ return ShouldDeviceBeIgnored(d.name);
+}
+#endif
+
+bool DeviceManager::GetVideoCaptureDevices(std::vector<Device>* devices) {
+ devices->clear();
+#ifdef OSX
+ if (GetQTKitVideoDevices(devices)) {
+ // Now filter out any known incompatible devices
+ devices->erase(remove_if(devices->begin(), devices->end(), FilterDevice),
+ devices->end());
+ return true;
+ }
+ return false;
+#else
+ return GetVideoDevices(devices);
+#endif
+}
+
+bool DeviceManager::GetDefaultVideoCaptureDevice(Device* device) {
+ bool ret = false;
+#if WIN32
+ // If there are multiple capture devices, we want the first USB one.
+ // This avoids issues with defaulting to virtual cameras or grabber cards.
+ std::vector<Device> devices;
+ ret = (GetVideoDevices(&devices) && !devices.empty());
+ if (ret) {
+ *device = devices[0];
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (strnicmp(devices[i].id.c_str(), kUsbDevicePathPrefix,
+ ARRAY_SIZE(kUsbDevicePathPrefix) - 1) == 0) {
+ *device = devices[i];
+ break;
+ }
+ }
+ }
+#else
+ // We just return the first device.
+ std::vector<Device> devices;
+ ret = (GetVideoCaptureDevices(&devices) && !devices.empty());
+ if (ret) {
+ *device = devices[0];
+ }
+#endif
+ return ret;
+}
+
+bool DeviceManager::GetVideoCaptureDevice(const std::string& name,
+ Device* out) {
+ // If the name is empty, return the default device.
+ if (name.empty() || name == kDefaultDeviceName) {
+ return GetDefaultVideoCaptureDevice(out);
+ }
+
+ std::vector<Device> devices;
+ if (!GetVideoCaptureDevices(&devices)) {
+ return false;
+ }
+
+ for (std::vector<Device>::const_iterator it = devices.begin();
+ it != devices.end(); ++it) {
+ if (name == it->name) {
+ *out = *it;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool DeviceManager::GetAudioDevice(bool is_input, const std::string& name,
+ Device* out) {
+ // If the name is empty, return the default device id.
+ if (name.empty() || name == kDefaultDeviceName) {
+ *out = Device(name, -1);
+ return true;
+ }
+
+ std::vector<Device> devices;
+ bool ret = is_input ? GetAudioInputDevices(&devices) :
+ GetAudioOutputDevices(&devices);
+ if (ret) {
+ ret = false;
+ for (size_t i = 0; i < devices.size(); ++i) {
+ if (devices[i].name == name) {
+ *out = devices[i];
+ ret = true;
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+bool DeviceManager::GetAudioDevicesByPlatform(bool input,
+ std::vector<Device>* devs) {
+ devs->clear();
+
+#if defined(LINUX)
+ if (!sound_system_.get()) {
+ return false;
+ }
+ SoundSystemInterface::SoundDeviceLocatorList list;
+ bool success;
+ if (input) {
+ success = sound_system_->EnumerateCaptureDevices(&list);
+ } else {
+ success = sound_system_->EnumeratePlaybackDevices(&list);
+ }
+ if (!success) {
+ LOG(LS_ERROR) << "Can't enumerate devices";
+ sound_system_.release();
+ return false;
+ }
+ int index = 0;
+ for (SoundSystemInterface::SoundDeviceLocatorList::iterator i = list.begin();
+ i != list.end();
+ ++i, ++index) {
+ devs->push_back(Device((*i)->name(), index));
+ }
+ SoundSystemInterface::ClearSoundDeviceLocatorList(&list);
+ sound_system_.release();
+ return true;
+
+#elif defined(WIN32)
+ if (talk_base::IsWindowsVistaOrLater()) {
+ return GetCoreAudioDevices(input, devs);
+ } else {
+ return GetWaveDevices(input, devs);
+ }
+
+#elif defined(OSX)
+ std::vector<AudioDeviceID> dev_ids;
+ bool ret = GetAudioDeviceIDs(input, &dev_ids);
+ if (ret) {
+ for (size_t i = 0; i < dev_ids.size(); ++i) {
+ std::string name;
+ if (GetAudioDeviceName(dev_ids[i], input, &name)) {
+ devs->push_back(Device(name, dev_ids[i]));
+ }
+ }
+ }
+ return ret;
+
+#else
+ return false;
+#endif
+}
+
+#if defined(WIN32)
+bool GetVideoDevices(std::vector<Device>* devices) {
+ return GetDevices(CLSID_VideoInputDeviceCategory, devices);
+}
+
+bool GetDevices(const CLSID& catid, std::vector<Device>* devices) {
+ HRESULT hr;
+
+ // CComPtr is a scoped pointer that will be auto released when going
+ // out of scope. CoUninitialize must not be called before the
+ // release.
+ CComPtr<ICreateDevEnum> sys_dev_enum;
+ CComPtr<IEnumMoniker> cam_enum;
+ if (FAILED(hr = sys_dev_enum.CoCreateInstance(CLSID_SystemDeviceEnum)) ||
+ FAILED(hr = sys_dev_enum->CreateClassEnumerator(catid, &cam_enum, 0))) {
+ LOG(LS_ERROR) << "Failed to create device enumerator, hr=" << hr;
+ return false;
+ }
+
+ // Only enum devices if CreateClassEnumerator returns S_OK. If there are no
+ // devices available, S_FALSE will be returned, but enumMk will be NULL.
+ if (hr == S_OK) {
+ CComPtr<IMoniker> mk;
+ while (cam_enum->Next(1, &mk, NULL) == S_OK) {
+ CComPtr<IPropertyBag> bag;
+ if (SUCCEEDED(mk->BindToStorage(NULL, NULL,
+ __uuidof(bag), reinterpret_cast<void**>(&bag)))) {
+ CComVariant name, path;
+ std::string name_str, path_str;
+ if (SUCCEEDED(bag->Read(kFriendlyName, &name, 0)) &&
+ name.vt == VT_BSTR) {
+ name_str = talk_base::ToUtf8(name.bstrVal);
+ if (!ShouldDeviceBeIgnored(name_str)) {
+ // Get the device id if one exists.
+ if (SUCCEEDED(bag->Read(kDevicePath, &path, 0)) &&
+ path.vt == VT_BSTR) {
+ path_str = talk_base::ToUtf8(path.bstrVal);
+ }
+
+ devices->push_back(Device(name_str, path_str));
+ }
+ }
+ }
+ mk = NULL;
+ }
+ }
+
+ return true;
+}
+
+HRESULT GetStringProp(IPropertyStore* bag, PROPERTYKEY key, std::string* out) {
+ out->clear();
+ PROPVARIANT var;
+ PropVariantInit(&var);
+
+ HRESULT hr = bag->GetValue(key, &var);
+ if (SUCCEEDED(hr)) {
+ if (var.pwszVal)
+ *out = talk_base::ToUtf8(var.pwszVal);
+ else
+ hr = E_FAIL;
+ }
+
+ PropVariantClear(&var);
+ return hr;
+}
+
+// Adapted from http://msdn.microsoft.com/en-us/library/dd370812(v=VS.85).aspx
+HRESULT CricketDeviceFromImmDevice(IMMDevice* device, Device* out) {
+ CComPtr<IPropertyStore> props;
+
+ HRESULT hr = device->OpenPropertyStore(STGM_READ, &props);
+ if (FAILED(hr)) {
+ return hr;
+ }
+
+ // Get the endpoint's name and id.
+ std::string name, guid;
+ hr = GetStringProp(props, PKEY_Device_FriendlyName, &name);
+ if (SUCCEEDED(hr)) {
+ hr = GetStringProp(props, PKEY_AudioEndpoint_GUID, &guid);
+
+ if (SUCCEEDED(hr)) {
+ out->name = name;
+ out->id = guid;
+ }
+ }
+ return hr;
+}
+
+bool GetCoreAudioDevices(bool input, std::vector<Device>* devs) {
+ HRESULT hr = S_OK;
+ CComPtr<IMMDeviceEnumerator> enumerator;
+
+ hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL,
+ __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&enumerator));
+ if (SUCCEEDED(hr)) {
+ CComPtr<IMMDeviceCollection> devices;
+ hr = enumerator->EnumAudioEndpoints((input ? eCapture : eRender),
+ DEVICE_STATE_ACTIVE, &devices);
+ if (SUCCEEDED(hr)) {
+ unsigned int count;
+ hr = devices->GetCount(&count);
+
+ if (SUCCEEDED(hr)) {
+ for (unsigned int i = 0; i < count; i++) {
+ CComPtr<IMMDevice> device;
+
+ // Get pointer to endpoint number i.
+ hr = devices->Item(i, &device);
+ if (FAILED(hr)) {
+ break;
+ }
+
+ Device dev;
+ hr = CricketDeviceFromImmDevice(device, &dev);
+ if (SUCCEEDED(hr)) {
+ devs->push_back(dev);
+ } else {
+ LOG(LS_WARNING) << "Unable to query IMM Device, skipping. HR="
+ << hr;
+ hr = S_FALSE;
+ }
+ }
+ }
+ }
+ }
+
+ if (!SUCCEEDED(hr)) {
+ LOG(LS_WARNING) << "GetCoreAudioDevices failed with hr " << hr;
+ return false;
+ }
+ return true;
+}
+
+bool GetWaveDevices(bool input, std::vector<Device>* devs) {
+ // Note, we don't use the System Device Enumerator interface here since it
+ // adds lots of pseudo-devices to the list, such as DirectSound and Wave
+ // variants of the same device.
+ if (input) {
+ int num_devs = waveInGetNumDevs();
+ for (int i = 0; i < num_devs; ++i) {
+ WAVEINCAPS caps;
+ if (waveInGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+ caps.wChannels > 0) {
+ devs->push_back(Device(talk_base::ToUtf8(caps.szPname),
+ talk_base::ToString(i)));
+ }
+ }
+ } else {
+ int num_devs = waveOutGetNumDevs();
+ for (int i = 0; i < num_devs; ++i) {
+ WAVEOUTCAPS caps;
+ if (waveOutGetDevCaps(i, &caps, sizeof(caps)) == MMSYSERR_NOERROR &&
+ caps.wChannels > 0) {
+ devs->push_back(Device(talk_base::ToUtf8(caps.szPname), i));
+ }
+ }
+ }
+ return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* manager)
+ : manager_(manager), audio_notify_(NULL), video_notify_(NULL) {
+}
+
+bool DeviceWatcher::Start() {
+ if (!Create(NULL, _T("libjingle DeviceWatcher Window"),
+ 0, 0, 0, 0, 0, 0)) {
+ return false;
+ }
+
+ audio_notify_ = Register(KSCATEGORY_AUDIO);
+ if (!audio_notify_) {
+ Stop();
+ return false;
+ }
+
+ video_notify_ = Register(KSCATEGORY_VIDEO);
+ if (!video_notify_) {
+ Stop();
+ return false;
+ }
+
+ return true;
+}
+
+void DeviceWatcher::Stop() {
+ UnregisterDeviceNotification(video_notify_);
+ video_notify_ = NULL;
+ UnregisterDeviceNotification(audio_notify_);
+ audio_notify_ = NULL;
+ Destroy();
+}
+
+HDEVNOTIFY DeviceWatcher::Register(REFGUID guid) {
+ DEV_BROADCAST_DEVICEINTERFACE dbdi;
+ dbdi.dbcc_size = sizeof(dbdi);
+ dbdi.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
+ dbdi.dbcc_classguid = guid;
+ dbdi.dbcc_name[0] = '\0';
+ return RegisterDeviceNotification(handle(), &dbdi,
+ DEVICE_NOTIFY_WINDOW_HANDLE);
+}
+
+void DeviceWatcher::Unregister(HDEVNOTIFY handle) {
+ UnregisterDeviceNotification(handle);
+}
+
+bool DeviceWatcher::OnMessage(UINT uMsg, WPARAM wParam, LPARAM lParam,
+ LRESULT& result) {
+ if (uMsg == WM_DEVICECHANGE) {
+ if (wParam == DBT_DEVICEARRIVAL ||
+ wParam == DBT_DEVICEREMOVECOMPLETE) {
+ DEV_BROADCAST_DEVICEINTERFACE* dbdi =
+ reinterpret_cast<DEV_BROADCAST_DEVICEINTERFACE*>(lParam);
+ if (dbdi->dbcc_classguid == KSCATEGORY_AUDIO ||
+ dbdi->dbcc_classguid == KSCATEGORY_VIDEO) {
+ manager_->OnDevicesChange();
+ }
+ }
+ result = 0;
+ return true;
+ }
+
+ return false;
+}
+#elif defined(OSX)
+static bool GetAudioDeviceIDs(bool input,
+ std::vector<AudioDeviceID>* out_dev_ids) {
+ UInt32 propsize;
+ OSErr err = AudioHardwareGetPropertyInfo(kAudioHardwarePropertyDevices,
+ &propsize, NULL);
+ if (0 != err) {
+ LOG(LS_ERROR) << "Couldn't get information about property, "
+ << "so no device list acquired.";
+ return false;
+ }
+
+ size_t num_devices = propsize / sizeof(AudioDeviceID);
+ talk_base::scoped_array<AudioDeviceID> device_ids(
+ new AudioDeviceID[num_devices]);
+
+ err = AudioHardwareGetProperty(kAudioHardwarePropertyDevices,
+ &propsize, device_ids.get());
+ if (0 != err) {
+ LOG(LS_ERROR) << "Failed to get device ids, "
+ << "so no device listing acquired.";
+ return false;
+ }
+
+ for (size_t i = 0; i < num_devices; ++i) {
+ AudioDeviceID an_id = device_ids[i];
+ // find out the number of channels for this direction
+ // (input/output) on this device -
+ // we'll ignore anything with no channels.
+ err = AudioDeviceGetPropertyInfo(an_id, 0, input,
+ kAudioDevicePropertyStreams,
+ &propsize, NULL);
+ if (0 == err) {
+ unsigned num_channels = propsize / sizeof(AudioStreamID);
+ if (0 < num_channels) {
+ out_dev_ids->push_back(an_id);
+ }
+ } else {
+ LOG(LS_ERROR) << "No property info for stream property for device id "
+ << an_id << "(is_input == " << input
+ << "), so not including it in the list.";
+ }
+ }
+
+ return true;
+}
+
+static bool GetAudioDeviceName(AudioDeviceID id,
+ bool input,
+ std::string* out_name) {
+ UInt32 nameLength = kAudioDeviceNameLength;
+ char name[kAudioDeviceNameLength + 1];
+ OSErr err = AudioDeviceGetProperty(id, 0, input,
+ kAudioDevicePropertyDeviceName,
+ &nameLength, name);
+ if (0 != err) {
+ LOG(LS_ERROR) << "No name acquired for device id " << id;
+ return false;
+ }
+
+ *out_name = name;
+ return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* manager)
+ : manager_(manager), impl_(NULL) {
+}
+
+bool DeviceWatcher::Start() {
+ if (!impl_) {
+ impl_ = CreateDeviceWatcherCallback(manager_);
+ }
+ return impl_ != NULL;
+}
+
+void DeviceWatcher::Stop() {
+ if (impl_) {
+ ReleaseDeviceWatcherCallback(impl_);
+ impl_ = NULL;
+ }
+}
+
+#elif defined(LINUX)
+static const std::string kVideoMetaPathK2_4("/proc/video/dev/");
+static const std::string kVideoMetaPathK2_6("/sys/class/video4linux/");
+
+enum MetaType { M2_4, M2_6, NONE };
+
+static void ScanDeviceDirectory(const std::string& devdir,
+ std::vector<Device>* devices) {
+ talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+ talk_base::Filesystem::IterateDirectory());
+
+ if (directoryIterator->Iterate(talk_base::Pathname(devdir))) {
+ do {
+ std::string filename = directoryIterator->Name();
+ std::string device_name = devdir + filename;
+ if (!directoryIterator->IsDots()) {
+ if (filename.find("video") == 0 &&
+ V4LLookup::IsV4L2Device(device_name)) {
+ devices->push_back(Device(device_name, device_name));
+ }
+ }
+ } while (directoryIterator->Next());
+ }
+}
+
+static std::string GetVideoDeviceNameK2_6(const std::string& device_meta_path) {
+ std::string device_name;
+
+ talk_base::scoped_ptr<talk_base::FileStream> device_meta_stream(
+ talk_base::Filesystem::OpenFile(device_meta_path, "r"));
+
+ if (device_meta_stream.get() != NULL) {
+ if (device_meta_stream->ReadLine(&device_name) != talk_base::SR_SUCCESS) {
+ LOG(LS_ERROR) << "Failed to read V4L2 device meta " << device_meta_path;
+ }
+ device_meta_stream->Close();
+ }
+
+ return device_name;
+}
+
+static std::string Trim(const std::string& s, const std::string& drop = " \t") {
+ std::string::size_type first = s.find_first_not_of(drop);
+ std::string::size_type last = s.find_last_not_of(drop);
+
+ if (first == std::string::npos || last == std::string::npos)
+ return std::string("");
+
+ return s.substr(first, last - first + 1);
+}
+
+static std::string GetVideoDeviceNameK2_4(const std::string& device_meta_path) {
+ talk_base::ConfigParser::MapVector all_values;
+
+ talk_base::ConfigParser config_parser;
+ talk_base::FileStream* file_stream =
+ talk_base::Filesystem::OpenFile(device_meta_path, "r");
+
+ if (file_stream == NULL) return "";
+
+ config_parser.Attach(file_stream);
+ config_parser.Parse(&all_values);
+
+ for (talk_base::ConfigParser::MapVector::iterator i = all_values.begin();
+ i != all_values.end(); ++i) {
+ talk_base::ConfigParser::SimpleMap::iterator device_name_i =
+ i->find("name");
+
+ if (device_name_i != i->end()) {
+ return device_name_i->second;
+ }
+ }
+
+ return "";
+}
+
+static std::string GetVideoDeviceName(MetaType meta,
+ const std::string& device_file_name) {
+ std::string device_meta_path;
+ std::string device_name;
+ std::string meta_file_path;
+
+ if (meta == M2_6) {
+ meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/name";
+
+ LOG(LS_INFO) << "Trying " + meta_file_path;
+ device_name = GetVideoDeviceNameK2_6(meta_file_path);
+
+ if (device_name.empty()) {
+ meta_file_path = kVideoMetaPathK2_6 + device_file_name + "/model";
+
+ LOG(LS_INFO) << "Trying " << meta_file_path;
+ device_name = GetVideoDeviceNameK2_6(meta_file_path);
+ }
+ } else {
+ meta_file_path = kVideoMetaPathK2_4 + device_file_name;
+ LOG(LS_INFO) << "Trying " << meta_file_path;
+ device_name = GetVideoDeviceNameK2_4(meta_file_path);
+ }
+
+ if (device_name.empty()) {
+ device_name = "/dev/" + device_file_name;
+ LOG(LS_ERROR)
+ << "Device name not found, defaulting to device path " << device_name;
+ }
+
+ LOG(LS_INFO) << "Name for " << device_file_name << " is " << device_name;
+
+ return Trim(device_name);
+}
+
+static void ScanV4L2Devices(std::vector<Device>* devices) {
+ LOG(LS_INFO) << ("Enumerating V4L2 devices");
+
+ MetaType meta;
+ std::string metadata_dir;
+
+ talk_base::scoped_ptr<talk_base::DirectoryIterator> directoryIterator(
+ talk_base::Filesystem::IterateDirectory());
+
+ // Try and guess kernel version
+ if (directoryIterator->Iterate(kVideoMetaPathK2_6)) {
+ meta = M2_6;
+ metadata_dir = kVideoMetaPathK2_6;
+ } else if (directoryIterator->Iterate(kVideoMetaPathK2_4)) {
+ meta = M2_4;
+ metadata_dir = kVideoMetaPathK2_4;
+ } else {
+ meta = NONE;
+ }
+
+ if (meta != NONE) {
+ LOG(LS_INFO) << "V4L2 device metadata found at " << metadata_dir;
+
+ do {
+ std::string filename = directoryIterator->Name();
+
+ if (filename.find("video") == 0) {
+ std::string device_path = "/dev/" + filename;
+
+ if (V4LLookup::IsV4L2Device(device_path)) {
+ devices->push_back(
+ Device(GetVideoDeviceName(meta, filename), device_path));
+ }
+ }
+ } while (directoryIterator->Next());
+ } else {
+ LOG(LS_ERROR) << "Unable to detect v4l2 metadata directory";
+ }
+
+ if (devices->size() == 0) {
+ LOG(LS_INFO) << "Plan B. Scanning all video devices in /dev directory";
+ ScanDeviceDirectory("/dev/", devices);
+ }
+
+ LOG(LS_INFO) << "Total V4L2 devices found : " << devices->size();
+}
+
+static bool GetVideoDevices(std::vector<Device>* devices) {
+ ScanV4L2Devices(devices);
+ return true;
+}
+
+DeviceWatcher::DeviceWatcher(DeviceManager* dm)
+ : manager_(dm), udev_(NULL), udev_monitor_(NULL), registered_(false) {}
+
+bool DeviceWatcher::Start() {
+ // We deliberately return true in the failure paths here because libudev is
+ // not a critical component of a Linux system so it may not be present/usable,
+ // and we don't want to halt DeviceManager initialization in such a case.
+ if (!libudev_.Load()) {
+ LOG(LS_WARNING) << "libudev not present/usable; DeviceWatcher disabled";
+ return true;
+ }
+ udev_ = LATE(udev_new)();
+ if (!udev_) {
+ LOG_ERR(LS_ERROR) << "udev_new()";
+ return true;
+ }
+ // The second argument here is the event source. It can be either "kernel" or
+ // "udev", but "udev" is the only correct choice. Apps listen on udev and the
+ // udev daemon in turn listens on the kernel.
+ udev_monitor_ = LATE(udev_monitor_new_from_netlink)(udev_, "udev");
+ if (!udev_monitor_) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_new_from_netlink()";
+ return true;
+ }
+ // We only listen for changes in the video devices. Audio devices are more or
+ // less unimportant because receiving device change notifications really only
+ // matters for broadcasting updated send/recv capabilities based on whether
+ // there is at least one device available, and almost all computers have at
+ // least one audio device. Also, PulseAudio device notifications don't come
+ // from the udev daemon, they come from the PulseAudio daemon, so we'd only
+ // want to listen for audio device changes from udev if using ALSA. For
+ // simplicity, we don't bother with any audio stuff at all.
+ if (LATE(udev_monitor_filter_add_match_subsystem_devtype)(udev_monitor_,
+ "video4linux",
+ NULL) < 0) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_filter_add_match_subsystem_devtype()";
+ return true;
+ }
+ if (LATE(udev_monitor_enable_receiving)(udev_monitor_) < 0) {
+ LOG_ERR(LS_ERROR) << "udev_monitor_enable_receiving()";
+ return true;
+ }
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Add(this);
+ registered_ = true;
+ return true;
+}
+
+void DeviceWatcher::Stop() {
+ if (registered_) {
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Remove(this);
+ registered_ = false;
+ }
+ if (udev_monitor_) {
+ LATE(udev_monitor_unref)(udev_monitor_);
+ udev_monitor_ = NULL;
+ }
+ if (udev_) {
+ LATE(udev_unref)(udev_);
+ udev_ = NULL;
+ }
+ libudev_.Unload();
+}
+
+uint32 DeviceWatcher::GetRequestedEvents() {
+ return talk_base::DE_READ;
+}
+
+void DeviceWatcher::OnPreEvent(uint32 ff) {
+ // Nothing to do.
+}
+
+void DeviceWatcher::OnEvent(uint32 ff, int err) {
+ udev_device* device = LATE(udev_monitor_receive_device)(udev_monitor_);
+ if (!device) {
+ // Probably the socket connection to the udev daemon was terminated (perhaps
+ // the daemon crashed or is being restarted?).
+ LOG_ERR(LS_WARNING) << "udev_monitor_receive_device()";
+ // Stop listening to avoid potential livelock (an fd with EOF in it is
+ // always considered readable).
+ static_cast<talk_base::PhysicalSocketServer*>(
+ talk_base::Thread::Current()->socketserver())->Remove(this);
+ registered_ = false;
+ return;
+ }
+ // Else we read the device successfully.
+
+ // Since we already have our own filesystem-based device enumeration code, we
+ // simply re-enumerate rather than inspecting the device event.
+ LATE(udev_device_unref)(device);
+ manager_->OnDevicesChange();
+}
+
+int DeviceWatcher::GetDescriptor() {
+ return LATE(udev_monitor_get_fd)(udev_monitor_);
+}
+
+bool DeviceWatcher::IsDescriptorClosed() {
+ // If it is closed then we will just get an error in
+ // udev_monitor_receive_device and unregister, so we don't need to check for
+ // it separately.
+ return false;
+}
+
+#endif
+
+// TODO: Try to get hold of a copy of Final Cut to understand why we
+// crash while scanning their components on OS X.
+#if !defined(LINUX) && !defined(IOS)
+static bool ShouldDeviceBeIgnored(const std::string& device_name) {
+ static const char* const kFilteredDevices[] = {
+ "Google Camera Adapter", // Our own magiccams
+#ifdef WIN32
+ "Asus virtual Camera", // Bad Asus desktop virtual cam
+ "Bluetooth Video", // Bad Sony viao bluetooth sharing driver
+#elif OSX
+ "DVCPRO HD", // Final cut
+ "Sonix SN9C201p", // Crashes in OpenAComponent and CloseComponent
+#endif
+ };
+
+ for (int i = 0; i < ARRAY_SIZE(kFilteredDevices); ++i) {
+ if (strnicmp(device_name.c_str(), kFilteredDevices[i],
+ strlen(kFilteredDevices[i])) == 0) {
+ LOG(LS_INFO) << "Ignoring device " << device_name;
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+}; // namespace cricket
diff --git a/talk/session/phone/devicemanager.h b/talk/session/phone/devicemanager.h
new file mode 100644
index 0000000..cd41f6f
--- /dev/null
+++ b/talk/session/phone/devicemanager.h
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef TALK_SESSION_PHONE_DEVICEMANAGER_H_
+#define TALK_SESSION_PHONE_DEVICEMANAGER_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslot.h"
+#include "talk/base/stringencode.h"
+#ifdef LINUX
+#include "talk/sound/soundsystemfactory.h"
+#endif
+
+namespace cricket {
+
+class DeviceWatcher;
+
+// Used to represent an audio or video capture or render device.
+struct Device {
+ Device() {}
+ Device(const std::string& first, int second)
+ : name(first),
+ id(talk_base::ToString(second)) {
+ }
+ Device(const std::string& first, const std::string& second)
+ : name(first), id(second) {}
+
+ std::string name;
+ 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 {
+ public:
+ DeviceManager();
+ virtual ~DeviceManager();
+
+ // Initialization
+ virtual bool Init();
+ virtual void Terminate();
+ bool initialized() const { return initialized_; }
+
+ // Capabilities
+ virtual int GetCapabilities();
+
+ // Device enumeration
+ 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 GetVideoCaptureDevices(std::vector<Device>* devs);
+ bool GetVideoCaptureDevice(const std::string& name, Device* out);
+
+ sigslot::signal0<> SignalDevicesChange;
+
+ void OnDevicesChange() { SignalDevicesChange(); }
+
+ static const std::string kDefaultDeviceName;
+
+ protected:
+ virtual bool GetAudioDevice(bool is_input, const std::string& name,
+ Device* out);
+ virtual bool GetDefaultVideoCaptureDevice(Device* device);
+
+ private:
+ bool GetAudioDevicesByPlatform(bool input, std::vector<Device>* devs);
+
+ bool initialized_;
+#ifdef WIN32
+ bool need_couninitialize_;
+#endif
+ DeviceWatcher* watcher_;
+#ifdef LINUX
+ SoundSystemHandle sound_system_;
+#endif
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_DEVICEMANAGER_H_
diff --git a/talk/session/phone/devicemanager_mac.mm b/talk/session/phone/devicemanager_mac.mm
new file mode 100644
index 0000000..3537ed9
--- /dev/null
+++ b/talk/session/phone/devicemanager_mac.mm
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/session/phone/devicemanager.h"
+
+#import <QTKit/QTKit.h>
+
+#include "talk/base/logging.h"
+
+@interface DeviceWatcherImpl : NSObject {
+@private
+ cricket::DeviceManager* manager_;
+}
+- (id)init:(cricket::DeviceManager*) dm;
+- (void)onDevicesChanged:(NSNotification *)notification;
+@end
+
+@implementation DeviceWatcherImpl
+- (id)init:(cricket::DeviceManager*) dm {
+ if ((self = [super init])) {
+ manager_ = dm;
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onDevicesChanged:)
+ name:QTCaptureDeviceWasConnectedNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(onDevicesChanged:)
+ name:QTCaptureDeviceWasDisconnectedNotification
+ object:nil];
+ }
+ return self;
+}
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [super dealloc];
+}
+- (void)onDevicesChanged:(NSNotification *)notification {
+ manager_->OnDevicesChange();
+}
+@end
+
+namespace cricket {
+
+void* CreateDeviceWatcherCallback(DeviceManager* dm) {
+ return [[DeviceWatcherImpl alloc] init:dm];
+}
+void ReleaseDeviceWatcherCallback(void* watcher) {
+ DeviceWatcherImpl* watcher_impl = static_cast<DeviceWatcherImpl*>(watcher);
+ [watcher_impl release];
+}
+
+bool GetQTKitVideoDevices(std::vector<Device>* devices) {
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ NSArray* qt_capture_devices =
+ [QTCaptureDevice inputDevicesWithMediaType:QTMediaTypeVideo];
+ NSUInteger count = [qt_capture_devices count];
+ LOG(LS_INFO) << count << " capture device(s) found:";
+ for (NSUInteger i = 0; i < count; ++i) {
+ QTCaptureDevice* qt_capture_device = [qt_capture_devices objectAtIndex:i];
+
+ static const NSString* kFormat = @"localizedDisplayName: \"%@\", "
+ "modelUniqueID: \"%@\", uniqueID \"%@\", isConnected: %d, isOpen: %d, "
+ "isInUseByAnotherApplication: %d";
+ NSString* info = [NSString stringWithFormat:kFormat,
+ [qt_capture_device localizedDisplayName],
+ [qt_capture_device modelUniqueID],
+ [qt_capture_device uniqueID],
+ [qt_capture_device isConnected],
+ [qt_capture_device isOpen],
+ [qt_capture_device isInUseByAnotherApplication]];
+ LOG(LS_INFO) << [info cStringUsingEncoding:NSUTF8StringEncoding];
+
+ std::string name([[qt_capture_device localizedDisplayName]
+ cStringUsingEncoding:NSUTF8StringEncoding]);
+ devices->push_back(Device(name,
+ [[qt_capture_device uniqueID]
+ cStringUsingEncoding:NSUTF8StringEncoding]));
+ }
+
+ [pool drain];
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/filemediaengine.cc b/talk/session/phone/filemediaengine.cc
new file mode 100644
index 0000000..49d92b6
--- /dev/null
+++ b/talk/session/phone/filemediaengine.cc
@@ -0,0 +1,252 @@
+// 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 "talk/session/phone/filemediaengine.h"
+
+#include <climits>
+
+#include "talk/base/buffer.h"
+#include "talk/base/event.h"
+#include "talk/base/logging.h"
+#include "talk/base/pathutils.h"
+#include "talk/base/stream.h"
+#include "talk/session/phone/rtpdump.h"
+
+namespace cricket {
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileMediaEngine.
+///////////////////////////////////////////////////////////////////////////
+int FileMediaEngine::GetCapabilities() {
+ int capabilities = 0;
+ if (!voice_input_filename_.empty()) {
+ capabilities |= MediaEngine::AUDIO_SEND;
+ }
+ if (!voice_output_filename_.empty()) {
+ capabilities |= MediaEngine::AUDIO_RECV;
+ }
+ if (!video_input_filename_.empty()) {
+ capabilities |= MediaEngine::VIDEO_SEND;
+ }
+ if (!video_output_filename_.empty()) {
+ capabilities |= MediaEngine::VIDEO_RECV;
+ }
+ return capabilities;
+}
+
+VoiceMediaChannel* FileMediaEngine::CreateChannel() {
+ if (!voice_input_filename_.empty() || !voice_output_filename_.empty()) {
+ return new FileVoiceChannel(voice_input_filename_, voice_output_filename_);
+ } else {
+ return NULL;
+ }
+}
+
+VideoMediaChannel* FileMediaEngine::CreateVideoChannel(
+ VoiceMediaChannel* voice_ch) {
+ if (!video_input_filename_.empty() || !video_output_filename_.empty()) {
+ return new FileVideoChannel(video_input_filename_, video_output_filename_);
+ } else {
+ return NULL;
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Definition of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+class RtpSenderReceiver
+ : public talk_base::Thread, public talk_base::MessageHandler {
+ public:
+ RtpSenderReceiver(MediaChannel* channel, const std::string& in_file,
+ const std::string& out_file);
+
+ // Called by media channel. Context: media channel thread.
+ bool SetSend(bool send);
+ void OnPacketReceived(talk_base::Buffer* packet);
+
+ // Override virtual method of parent MessageHandler. Context: Worker Thread.
+ virtual void OnMessage(talk_base::Message* pmsg);
+
+ private:
+ // Read the next RTP dump packet, whose RTP SSRC is the same as first_ssrc_.
+ // Return true if successful.
+ bool ReadNextPacket(RtpDumpPacket* packet);
+ // Send a RTP packet to the network. The input parameter data points to the
+ // start of the RTP packet and len is the packet size. Return true if the sent
+ // size is equal to len.
+ bool SendRtpPacket(const void* data, size_t len);
+
+ MediaChannel* media_channel_;
+ talk_base::scoped_ptr<talk_base::StreamInterface> input_stream_;
+ talk_base::scoped_ptr<talk_base::StreamInterface> output_stream_;
+ talk_base::scoped_ptr<RtpDumpLoopReader> rtp_dump_reader_;
+ talk_base::scoped_ptr<RtpDumpWriter> rtp_dump_writer_;
+ // RTP dump packet read from the input stream.
+ RtpDumpPacket rtp_dump_packet_;
+ uint32 start_send_time_;
+ bool sending_;
+ bool first_packet_;
+ uint32 first_ssrc_;
+
+ DISALLOW_COPY_AND_ASSIGN(RtpSenderReceiver);
+};
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpSenderReceiver.
+///////////////////////////////////////////////////////////////////////////
+RtpSenderReceiver::RtpSenderReceiver(MediaChannel* channel,
+ const std::string& in_file,
+ const std::string& out_file)
+ : media_channel_(channel),
+ sending_(false),
+ first_packet_(true) {
+ input_stream_.reset(talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(in_file), "rb"));
+ if (input_stream_.get()) {
+ rtp_dump_reader_.reset(new RtpDumpLoopReader(input_stream_.get()));
+ // Start the sender thread, which reads rtp dump records, waits based on
+ // the record timestamps, and sends the RTP packets to the network.
+ Thread::Start();
+ }
+
+ // Create a rtp dump writer for the output RTP dump stream.
+ output_stream_.reset(talk_base::Filesystem::OpenFile(
+ talk_base::Pathname(out_file), "wb"));
+ if (output_stream_.get()) {
+ rtp_dump_writer_.reset(new RtpDumpWriter(output_stream_.get()));
+ }
+}
+
+bool RtpSenderReceiver::SetSend(bool send) {
+ bool was_sending = sending_;
+ sending_ = send;
+ if (!was_sending && sending_) {
+ PostDelayed(0, this); // Wake up the send thread.
+ start_send_time_ = talk_base::Time();
+ }
+ return true;
+}
+
+void RtpSenderReceiver::OnPacketReceived(talk_base::Buffer* packet) {
+ if (rtp_dump_writer_.get()) {
+ rtp_dump_writer_->WriteRtpPacket(packet->data(), packet->length());
+ }
+}
+
+void RtpSenderReceiver::OnMessage(talk_base::Message* pmsg) {
+ if (!sending_) {
+ // If the sender thread is not sending, ignore this message. The thread goes
+ // to sleep until SetSend(true) wakes it up.
+ return;
+ }
+
+ if (!first_packet_) {
+ // Send the previously read packet.
+ SendRtpPacket(&rtp_dump_packet_.data[0], rtp_dump_packet_.data.size());
+ }
+
+ if (ReadNextPacket(&rtp_dump_packet_)) {
+ int wait = talk_base::TimeUntil(
+ start_send_time_ + rtp_dump_packet_.elapsed_time);
+ wait = talk_base::_max(0, wait);
+ PostDelayed(wait, this);
+ } else {
+ Quit();
+ }
+}
+
+bool RtpSenderReceiver::ReadNextPacket(RtpDumpPacket* packet) {
+ while (talk_base::SR_SUCCESS == rtp_dump_reader_->ReadPacket(packet)) {
+ uint32 ssrc;
+ if (!packet->GetRtpSsrc(&ssrc)) {
+ return false;
+ }
+ if (first_packet_) {
+ first_packet_ = false;
+ first_ssrc_ = ssrc;
+ }
+ if (ssrc == first_ssrc_) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool RtpSenderReceiver::SendRtpPacket(const void* data, size_t len) {
+ if (!media_channel_ || !media_channel_->network_interface()) {
+ return false;
+ }
+
+ talk_base::Buffer packet(data, len, kMaxRtpPacketLen);
+ return media_channel_->network_interface()->SendPacket(&packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVoiceChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVoiceChannel::FileVoiceChannel(const std::string& in_file,
+ const std::string& out_file)
+ : rtp_sender_receiver_(new RtpSenderReceiver(this, in_file, out_file)) {
+}
+
+FileVoiceChannel::~FileVoiceChannel() {}
+
+bool FileVoiceChannel::SetSendCodecs(const std::vector<AudioCodec>& codecs) {
+ // TODO: Check the format of RTP dump input.
+ return true;
+}
+
+bool FileVoiceChannel::SetSend(SendFlags flag) {
+ return rtp_sender_receiver_->SetSend(flag != SEND_NOTHING);
+}
+
+void FileVoiceChannel::OnPacketReceived(talk_base::Buffer* packet) {
+ rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of FileVideoChannel.
+///////////////////////////////////////////////////////////////////////////
+FileVideoChannel::FileVideoChannel(const std::string& in_file,
+ const std::string& out_file)
+ : rtp_sender_receiver_(new RtpSenderReceiver(this, in_file, out_file)) {
+}
+
+FileVideoChannel::~FileVideoChannel() {}
+
+bool FileVideoChannel::SetSendCodecs(const std::vector<VideoCodec>& codecs) {
+ // TODO: Check the format of RTP dump input.
+ return true;
+}
+
+bool FileVideoChannel::SetSend(bool send) {
+ return rtp_sender_receiver_->SetSend(send);
+}
+
+void FileVideoChannel::OnPacketReceived(talk_base::Buffer* packet) {
+ rtp_sender_receiver_->OnPacketReceived(packet);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/filemediaengine.h b/talk/session/phone/filemediaengine.h
new file mode 100644
index 0000000..2276772
--- /dev/null
+++ b/talk/session/phone/filemediaengine.h
@@ -0,0 +1,197 @@
+// 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_SESSION_PHONE_FILEMEDIAENGINE_H_
+#define TALK_SESSION_PHONE_FILEMEDIAENGINE_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace talk_base {
+class StreamInterface;
+}
+
+namespace cricket {
+
+// A media engine contains a capturer, an encoder, and a sender in the sender
+// side and a receiver, a decoder, and a renderer in the receiver side.
+// FileMediaEngine simulates the capturer and the encoder via an input RTP dump
+// stream and simulates the decoder and the renderer via an output RTP dump
+// stream. Depending on the parameters of the constructor, FileMediaEngine can
+// act as file voice engine, file video engine, or both. Currently, we use
+// only the RTP dump packets. TODO: Enable RTCP packets.
+class FileMediaEngine : public MediaEngine {
+ public:
+ FileMediaEngine() {}
+ virtual ~FileMediaEngine() {}
+
+ // Set the file name of the input or output RTP dump for voice or video.
+ // Should be called before the channel is created.
+ void set_voice_input_filename(const std::string& filename) {
+ voice_input_filename_ = filename;
+ }
+ void set_voice_output_filename(const std::string& filename) {
+ voice_output_filename_ = filename;
+ }
+ void set_video_input_filename(const std::string& filename) {
+ video_input_filename_ = filename;
+ }
+ void set_video_output_filename(const std::string& filename) {
+ video_output_filename_ = filename;
+ }
+
+ // Should be called before codecs() and video_codecs() are called. We need to
+ // set the voice and video codecs; otherwise, Jingle initiation will fail.
+ void set_voice_codecs(const std::vector<AudioCodec>& codecs) {
+ voice_codecs_ = codecs;
+ }
+ void set_video_codecs(const std::vector<VideoCodec>& codecs) {
+ video_codecs_ = codecs;
+ }
+
+ // Implement pure virtual methods of MediaEngine.
+ virtual bool Init() { return true; }
+ virtual void Terminate() {}
+ virtual int GetCapabilities();
+ virtual VoiceMediaChannel* CreateChannel();
+ virtual VideoMediaChannel* CreateVideoChannel(VoiceMediaChannel* voice_ch);
+ virtual SoundclipMedia* CreateSoundclip() { return NULL; }
+ virtual bool SetAudioOptions(int options) { return true; }
+ virtual bool SetVideoOptions(int options) { return true; }
+ virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+ return true;
+ }
+ virtual bool SetSoundDevices(const Device* in_dev, const Device* out_dev) {
+ return true;
+ }
+ virtual bool SetVideoCaptureDevice(const Device* cam_device) { return true; }
+ virtual bool SetOutputVolume(int level) { return true; }
+ virtual int GetInputLevel() { return 0; }
+ virtual bool SetLocalMonitor(bool enable) { return true; }
+ virtual bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+ // TODO: control channel send?
+ virtual CaptureResult SetVideoCapture(bool capture) { return CR_SUCCESS; }
+ virtual const std::vector<AudioCodec>& audio_codecs() {
+ return voice_codecs_;
+ }
+ virtual const std::vector<VideoCodec>& video_codecs() {
+ return video_codecs_;
+ }
+ virtual bool FindAudioCodec(const AudioCodec& codec) { return true; }
+ virtual bool FindVideoCodec(const VideoCodec& codec) { return true; }
+ virtual void SetVoiceLogging(int min_sev, const char* filter) {}
+ virtual void SetVideoLogging(int min_sev, const char* filter) {}
+
+ private:
+ std::string voice_input_filename_;
+ std::string voice_output_filename_;
+ std::string video_input_filename_;
+ std::string video_output_filename_;
+ std::vector<AudioCodec> voice_codecs_;
+ std::vector<VideoCodec> video_codecs_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileMediaEngine);
+};
+
+class RtpSenderReceiver; // Forward declaration. Defined in the .cc file.
+
+class FileVoiceChannel : public VoiceMediaChannel {
+ public:
+ FileVoiceChannel(const std::string& in_file, const std::string& out_file);
+ virtual ~FileVoiceChannel();
+
+ // Implement pure virtual methods of VoiceMediaChannel.
+ virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) {
+ return true;
+ }
+ virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs);
+ virtual bool SetPlayout(bool playout) { return true; }
+ virtual bool SetSend(SendFlags flag);
+ virtual bool AddStream(uint32 ssrc) { return true; }
+ virtual bool RemoveStream(uint32 ssrc) { return true; }
+ virtual bool GetActiveStreams(AudioInfo::StreamList* actives) { return true; }
+ virtual int GetOutputLevel() { return 0; }
+ virtual void SetRingbackTone(const char* buf, int len) {}
+ virtual bool PlayRingbackTone(bool play, bool loop) { return true; }
+ virtual bool PressDTMF(int event, bool playout) { return true; }
+ virtual bool GetStats(VoiceMediaInfo* info) { return true; }
+
+ // Implement pure virtual methods of MediaChannel.
+ virtual void OnPacketReceived(talk_base::Buffer* packet);
+ virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+ virtual void SetSendSsrc(uint32 id) {} // TODO: change RTP packet?
+ virtual bool SetRtcpCName(const std::string& cname) { return true; }
+ virtual bool Mute(bool on) { return false; }
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+ virtual bool SetOptions(int options) { return true; }
+
+ private:
+ talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+ DISALLOW_COPY_AND_ASSIGN(FileVoiceChannel);
+};
+
+class FileVideoChannel : public VideoMediaChannel {
+ public:
+ FileVideoChannel(const std::string& in_file, const std::string& out_file);
+ virtual ~FileVideoChannel();
+
+ // Implement pure virtual methods of VideoMediaChannel.
+ virtual bool SetRecvCodecs(const std::vector<VideoCodec>& codecs) {
+ return true;
+ }
+ virtual bool SetSendCodecs(const std::vector<VideoCodec>& codecs);
+ virtual bool SetRender(bool render) { return true; }
+ virtual bool SetSend(bool send);
+ virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) { return true; }
+ virtual bool RemoveStream(uint32 ssrc) { return true; }
+ virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) {
+ return true;
+ }
+ virtual bool GetStats(VideoMediaInfo* info) { return true; }
+ virtual bool SendIntraFrame() { return false; }
+ virtual bool RequestIntraFrame() { return false; }
+
+ // Implement pure virtual methods of MediaChannel.
+ virtual void OnPacketReceived(talk_base::Buffer* packet);
+ virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+ virtual void SetSendSsrc(uint32 id) {} // TODO: change RTP packet?
+ virtual bool SetRtcpCName(const std::string& cname) { return true; }
+ virtual bool Mute(bool on) { return false; }
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+ virtual bool SetOptions(int options) { return true; }
+
+ private:
+ talk_base::scoped_ptr<RtpSenderReceiver> rtp_sender_receiver_;
+ DISALLOW_COPY_AND_ASSIGN(FileVideoChannel);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_FILEMEDIAENGINE_H_
diff --git a/talk/session/phone/libudevsymboltable.cc b/talk/session/phone/libudevsymboltable.cc
new file mode 100644
index 0000000..b312306
--- /dev/null
+++ b/talk/session/phone/libudevsymboltable.cc
@@ -0,0 +1,39 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/session/phone/libudevsymboltable.h"
+
+namespace cricket {
+
+LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(LibUDevSymbolTable, "libudev.so.0")
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(LibUDevSymbolTable, sym)
+LIBUDEV_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DEFINE_END(LibUDevSymbolTable)
+
+} // namespace cricket
diff --git a/talk/session/phone/libudevsymboltable.h b/talk/session/phone/libudevsymboltable.h
new file mode 100644
index 0000000..0dbef6c
--- /dev/null
+++ b/talk/session/phone/libudevsymboltable.h
@@ -0,0 +1,58 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_LIBUDEVSYMBOLTABLE_H_
+#define TALK_SESSION_PHONE_LIBUDEVSYMBOLTABLE_H_
+
+#include "talk/base/latebindingsymboltable.h"
+
+namespace cricket {
+
+// The libudev symbols we need, as an X-Macro list.
+// This list must contain precisely every libudev function that is used in
+// devicemanager.cc.
+#define LIBUDEV_SYMBOLS_LIST \
+ X(udev_device_unref) \
+ X(udev_monitor_enable_receiving) \
+ X(udev_monitor_filter_add_match_subsystem_devtype) \
+ X(udev_monitor_get_fd) \
+ X(udev_monitor_new_from_netlink) \
+ X(udev_monitor_receive_device) \
+ X(udev_monitor_unref) \
+ X(udev_new) \
+ X(udev_unref)
+
+LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(LibUDevSymbolTable)
+#define X(sym) \
+ LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(LibUDevSymbolTable, sym)
+LIBUDEV_SYMBOLS_LIST
+#undef X
+LATE_BINDING_SYMBOL_TABLE_DECLARE_END(LibUDevSymbolTable)
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_LIBUDEVSYMBOLTABLE_H_
diff --git a/talk/session/phone/mediachannel.h b/talk/session/phone/mediachannel.h
new file mode 100644
index 0000000..bb17db2
--- /dev/null
+++ b/talk/session/phone/mediachannel.h
@@ -0,0 +1,466 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_MEDIACHANNEL_H_
+#define TALK_SESSION_PHONE_MEDIACHANNEL_H_
+
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/socket.h"
+#include "talk/session/phone/codec.h"
+// TODO: re-evaluate this include
+#include "talk/session/phone/audiomonitor.h"
+
+namespace talk_base {
+class Buffer;
+}
+
+namespace flute {
+class MagicCamVideoRenderer;
+}
+
+namespace cricket {
+
+const size_t kMinRtpPacketLen = 12;
+const size_t kMinRtcpPacketLen = 4;
+const size_t kMaxRtpPacketLen = 2048;
+
+enum VoiceMediaChannelOptions {
+ OPT_CONFERENCE = 0x10000, // tune the audio stream for conference mode
+ OPT_ENERGYLEVEL = 0x20000, // include the energy level in RTP packets, as
+ // defined in https://datatracker.ietf.org/drafts/
+ // draft-lennox-avt-rtp-audio-level-exthdr/
+
+};
+
+enum VideoMediaChannelOptions {
+ OPT_INTERPOLATE = 0x10000 // Increase the output framerate by 2x by
+ // interpolating frames
+};
+
+class MediaChannel : public sigslot::has_slots<> {
+ public:
+ class NetworkInterface {
+ public:
+ enum SocketType { ST_RTP, ST_RTCP };
+ virtual bool SendPacket(talk_base::Buffer* packet) = 0;
+ virtual bool SendRtcp(talk_base::Buffer* packet) = 0;
+ virtual int SetOption(SocketType type, talk_base::Socket::Option opt,
+ int option) = 0;
+ virtual ~NetworkInterface() {}
+ };
+
+ MediaChannel() : network_interface_(NULL) {}
+ virtual ~MediaChannel() {}
+
+ // Gets/sets the abstract inteface class for sending RTP/RTCP data.
+ NetworkInterface *network_interface() { return network_interface_; }
+ virtual void SetInterface(NetworkInterface *iface) {
+ network_interface_ = iface;
+ }
+
+ // Called when a RTP packet is received.
+ virtual void OnPacketReceived(talk_base::Buffer* packet) = 0;
+ // Called when a RTCP packet is received.
+ virtual void OnRtcpReceived(talk_base::Buffer* packet) = 0;
+ // Sets the SSRC to be used for outgoing data.
+ virtual void SetSendSsrc(uint32 id) = 0;
+ // Set the CNAME of RTCP
+ virtual bool SetRtcpCName(const std::string& cname) = 0;
+ // Mutes the channel.
+ virtual bool Mute(bool on) = 0;
+
+ virtual bool SetRtpExtensionHeaders(bool enable_all) { return true; }
+ virtual bool SetSendBandwidth(bool autobw, int bps) = 0;
+ virtual bool SetOptions(int options) = 0;
+
+ protected:
+ NetworkInterface *network_interface_;
+};
+
+enum SendFlags {
+ SEND_NOTHING,
+ SEND_RINGBACKTONE,
+ SEND_MICROPHONE
+};
+
+struct VoiceSenderInfo {
+ uint32 ssrc;
+ int bytes_sent;
+ int packets_sent;
+ int packets_lost;
+ float fraction_lost;
+ int ext_seqnum;
+ int rtt_ms;
+ int jitter_ms;
+ int audio_level;
+};
+
+struct VoiceReceiverInfo {
+ uint32 ssrc;
+ int bytes_rcvd;
+ int packets_rcvd;
+ int packets_lost;
+ float fraction_lost;
+ int ext_seqnum;
+ int jitter_ms;
+ int audio_level;
+};
+
+struct VideoSenderInfo {
+ uint32 ssrc;
+ int bytes_sent;
+ int packets_sent;
+ int packets_cached;
+ int packets_lost;
+ float fraction_lost;
+ int firs_rcvd;
+ int nacks_rcvd;
+ int rtt_ms;
+ int frame_width;
+ int frame_height;
+ int framerate_input;
+ int framerate_sent;
+};
+
+struct VideoReceiverInfo {
+ uint32 ssrc;
+ int bytes_rcvd;
+ // vector<int> layer_bytes_rcvd;
+ int packets_rcvd;
+ int packets_lost;
+ int packets_concealed;
+ float fraction_lost;
+ int firs_sent;
+ int nacks_sent;
+ int frame_width;
+ int frame_height;
+ int framerate_rcvd;
+ int framerate_decoded;
+ int framerate_output;
+};
+
+struct VoiceMediaInfo {
+ void Clear() {
+ senders.clear();
+ receivers.clear();
+ }
+ std::vector<VoiceSenderInfo> senders;
+ std::vector<VoiceReceiverInfo> receivers;
+};
+
+struct VideoMediaInfo {
+ void Clear() {
+ senders.clear();
+ receivers.clear();
+ }
+ std::vector<VideoSenderInfo> senders;
+ std::vector<VideoReceiverInfo> receivers;
+};
+
+class VoiceMediaChannel : public MediaChannel {
+ public:
+ enum Error {
+ ERROR_NONE = 0, // No error.
+ ERROR_OTHER, // Other errors.
+ ERROR_REC_DEVICE_OPEN_FAILED = 100, // Could not open mic.
+ ERROR_REC_DEVICE_MUTED, // Mic was muted by OS.
+ ERROR_REC_DEVICE_SILENT, // No background noise picked up.
+ ERROR_REC_DEVICE_SATURATION, // Mic input is clipping.
+ ERROR_REC_DEVICE_REMOVED, // Mic was removed while active.
+ ERROR_REC_RUNTIME_ERROR, // Processing is encountering errors.
+ ERROR_REC_SRTP_ERROR, // Generic SRTP failure.
+ ERROR_PLAY_DEVICE_OPEN_FAILED = 200, // Could not open playout.
+ ERROR_PLAY_DEVICE_MUTED, // Playout muted by OS.
+ ERROR_PLAY_DEVICE_REMOVED, // Playout removed while active.
+ ERROR_PLAY_RUNTIME_ERROR, // Errors in voice processing.
+ ERROR_PLAY_SRTP_ERROR, // Generic SRTP failure.
+ ERROR_PLAY_SRTP_AUTH_FAILED, // Failed to authenticate packets.
+ };
+
+ VoiceMediaChannel() {}
+ virtual ~VoiceMediaChannel() {}
+ // Sets the codecs/payload types to be used for incoming media.
+ virtual bool SetRecvCodecs(const std::vector<AudioCodec>& codecs) = 0;
+ // Sets the codecs/payload types to be used for outgoing media.
+ virtual bool SetSendCodecs(const std::vector<AudioCodec>& codecs) = 0;
+ // Starts or stops playout of received audio.
+ virtual bool SetPlayout(bool playout) = 0;
+ // Starts or stops sending (and potentially capture) of local audio.
+ virtual bool SetSend(SendFlags flag) = 0;
+ // Adds a new receive-only stream with the specified SSRC.
+ virtual bool AddStream(uint32 ssrc) = 0;
+ // Removes a stream added with AddStream.
+ virtual bool RemoveStream(uint32 ssrc) = 0;
+ // Gets current energy levels for all incoming streams.
+ virtual bool GetActiveStreams(AudioInfo::StreamList* actives) = 0;
+ // Get the current energy level for the outgoing stream.
+ virtual int GetOutputLevel() = 0;
+ // Specifies a ringback tone to be played during call setup.
+ virtual void SetRingbackTone(const char *buf, int len) = 0;
+ // Plays or stops the aforementioned ringback tone
+ virtual bool PlayRingbackTone(bool play, bool loop) = 0;
+ // Sends a out-of-band DTMF signal using the specified event.
+ virtual bool PressDTMF(int event, bool playout) = 0;
+ // Gets quality stats for the channel.
+ virtual bool GetStats(VoiceMediaInfo* info) = 0;
+ // Gets last reported error for this media channel.
+ virtual void GetLastMediaError(uint32* ssrc,
+ VoiceMediaChannel::Error* error) {
+ ASSERT(error != NULL);
+ *error = ERROR_NONE;
+ }
+
+ // Signal errors from MediaChannel. Arguments are:
+ // ssrc(uint32), and error(VoiceMediaChannel::Error).
+ sigslot::signal2<uint32, VoiceMediaChannel::Error> SignalMediaError;
+};
+
+// Represents a YUV420 (a.k.a. I420) video frame.
+class VideoFrame {
+ friend class flute::MagicCamVideoRenderer;
+
+ public:
+ VideoFrame() : rendered_(false) {}
+
+ virtual ~VideoFrame() {}
+
+ virtual size_t GetWidth() const = 0;
+ virtual size_t GetHeight() const = 0;
+ virtual const uint8 *GetYPlane() const = 0;
+ virtual const uint8 *GetUPlane() const = 0;
+ virtual const uint8 *GetVPlane() const = 0;
+ virtual uint8 *GetYPlane() = 0;
+ virtual uint8 *GetUPlane() = 0;
+ virtual uint8 *GetVPlane() = 0;
+ virtual int32 GetYPitch() const = 0;
+ virtual int32 GetUPitch() const = 0;
+ virtual int32 GetVPitch() const = 0;
+
+ // For retrieving the aspect ratio of each pixel. Usually this is 1x1, but
+ // the aspect_ratio_idc parameter of H.264 can specify non-square pixels.
+ virtual size_t GetPixelWidth() const = 0;
+ virtual size_t GetPixelHeight() const = 0;
+
+ // TODO: Add a fourcc format here and probably combine VideoFrame
+ // with CapturedFrame.
+ virtual int64 GetElapsedTime() const = 0;
+ virtual int64 GetTimeStamp() const = 0;
+ virtual void SetElapsedTime(int64 elapsed_time) = 0;
+ virtual void SetTimeStamp(int64 time_stamp) = 0;
+
+ // Make a copy of the frame. The frame buffer itself may not be copied,
+ // in which case both the current and new VideoFrame will share a single
+ // reference-counted frame buffer.
+ virtual VideoFrame *Copy() const = 0;
+
+ // Writes the frame into the given frame buffer, provided that it is of
+ // sufficient size. Returns the frame's actual size, regardless of whether
+ // it was written or not (like snprintf). If there is insufficient space,
+ // nothing is written.
+ virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const = 0;
+
+ // Converts the I420 data to RGB of a certain type such as ARGB and ABGR.
+ // Returns the frame's actual size, regardless of whether it was written or
+ // not (like snprintf). Parameters size and pitch_rgb are in units of bytes.
+ // If there is insufficient space, nothing is written.
+ virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
+ size_t size, size_t pitch_rgb) const = 0;
+
+ // Writes the frame into the given planes, stretched to the given width and
+ // height. The parameter "interpolate" controls whether to interpolate or just
+ // take the nearest-point. The parameter "crop" controls whether to crop this
+ // frame to the aspect ratio of the given dimensions before stretching.
+ virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
+ int32 pitchY, int32 pitchU, int32 pitchV,
+ size_t width, size_t height,
+ bool interpolate, bool crop) const = 0;
+
+ // Writes the frame into the given frame buffer, stretched to the given width
+ // and height, provided that it is of sufficient size. Returns the frame's
+ // actual size, regardless of whether it was written or not (like snprintf).
+ // If there is insufficient space, nothing is written. The parameter
+ // "interpolate" controls whether to interpolate or just take the
+ // nearest-point. The parameter "crop" controls whether to crop this frame to
+ // the aspect ratio of the given dimensions before stretching.
+ virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
+ bool interpolate, bool crop) const = 0;
+
+ // Writes the frame into the target VideoFrame, stretched to the size of that
+ // frame. The parameter "interpolate" controls whether to interpolate or just
+ // take the nearest-point. The parameter "crop" controls whether to crop this
+ // frame to the aspect ratio of the target frame before stretching.
+ virtual void StretchToFrame(VideoFrame *target, bool interpolate,
+ bool crop) const = 0;
+
+ // Stretches the frame to the given size, creating a new VideoFrame object to
+ // hold it. The parameter "interpolate" controls whether to interpolate or
+ // just take the nearest-point. The parameter "crop" controls whether to crop
+ // this frame to the aspect ratio of the given dimensions before stretching.
+ virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
+ bool crop) const = 0;
+
+ // Size of an I420 image of given dimensions when stored as a frame buffer.
+ static size_t SizeOf(size_t w, size_t h) {
+ return w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
+ }
+
+ protected:
+ // The frame needs to be rendered to magiccam only once.
+ // TODO: Remove this flag once magiccam rendering is fully replaced
+ // by client3d rendering.
+ mutable bool rendered_;
+};
+
+// Simple subclass for use in mocks.
+class NullVideoFrame : public VideoFrame {
+ public:
+ virtual size_t GetWidth() const { return 0; }
+ virtual size_t GetHeight() const { return 0; }
+ virtual const uint8 *GetYPlane() const { return NULL; }
+ virtual const uint8 *GetUPlane() const { return NULL; }
+ virtual const uint8 *GetVPlane() const { return NULL; }
+ virtual uint8 *GetYPlane() { return NULL; }
+ virtual uint8 *GetUPlane() { return NULL; }
+ virtual uint8 *GetVPlane() { return NULL; }
+ virtual int32 GetYPitch() const { return 0; }
+ virtual int32 GetUPitch() const { return 0; }
+ virtual int32 GetVPitch() const { return 0; }
+
+ virtual size_t GetPixelWidth() const { return 1; }
+ virtual size_t GetPixelHeight() const { return 1; }
+ virtual int64 GetElapsedTime() const { return 0; }
+ virtual int64 GetTimeStamp() const { return 0; }
+ virtual void SetElapsedTime(int64 elapsed_time) {}
+ virtual void SetTimeStamp(int64 time_stamp) {}
+
+ virtual VideoFrame *Copy() const {
+ return NULL;
+ }
+
+ virtual size_t CopyToBuffer(uint8 *buffer, size_t size) const {
+ return 0;
+ }
+
+ virtual size_t ConvertToRgbBuffer(uint32 to_fourcc, uint8 *buffer,
+ size_t size, size_t pitch_rgb) const {
+ return 0;
+ }
+
+ virtual void StretchToPlanes(uint8 *y, uint8 *u, uint8 *v,
+ int32 pitchY, int32 pitchU, int32 pitchV,
+ size_t width, size_t height,
+ bool interpolate, bool crop) const {
+ }
+
+ virtual size_t StretchToBuffer(size_t w, size_t h, uint8 *buffer, size_t size,
+ bool interpolate, bool crop) const {
+ return 0;
+ }
+
+ virtual void StretchToFrame(VideoFrame *target, bool interpolate,
+ bool crop) const {
+ }
+
+ virtual VideoFrame *Stretch(size_t w, size_t h, bool interpolate,
+ bool crop) const {
+ return NULL;
+ }
+};
+
+// Abstract interface for rendering VideoFrames.
+class VideoRenderer {
+ public:
+ virtual ~VideoRenderer() {}
+ // Called when the video has changed size.
+ virtual bool SetSize(int width, int height, int reserved) = 0;
+ // Called when a new frame is available for display.
+ virtual bool RenderFrame(const VideoFrame *frame) = 0;
+};
+
+// Simple implementation for use in tests.
+class NullVideoRenderer : public VideoRenderer {
+ virtual bool SetSize(int width, int height, int reserved) {
+ return true;
+ }
+ // Called when a new frame is available for display.
+ virtual bool RenderFrame(const VideoFrame *frame) {
+ return true;
+ }
+};
+
+class VideoMediaChannel : public MediaChannel {
+ public:
+ enum Error {
+ ERROR_NONE = 0, // No error.
+ ERROR_OTHER, // Other errors.
+ ERROR_REC_DEVICE_OPEN_FAILED = 100, // Could not open camera.
+ ERROR_REC_DEVICE_NO_DEVICE, // No camera.
+ ERROR_REC_DEVICE_IN_USE, // Device is in already use.
+ ERROR_REC_DEVICE_REMOVED, // Device is removed.
+ ERROR_REC_SRTP_ERROR, // Generic sender SRTP failure.
+ ERROR_PLAY_SRTP_ERROR = 200, // Generic receiver SRTP failure.
+ ERROR_PLAY_SRTP_AUTH_FAILED, // Failed to authenticate packets.
+ };
+
+ VideoMediaChannel() { renderer_ = NULL; }
+ virtual ~VideoMediaChannel() {}
+ // Sets the codecs/payload types to be used for incoming media.
+ virtual bool SetRecvCodecs(const std::vector<VideoCodec> &codecs) = 0;
+ // Sets the codecs/payload types to be used for outgoing media.
+ virtual bool SetSendCodecs(const std::vector<VideoCodec> &codecs) = 0;
+ // Starts or stops playout of received video.
+ virtual bool SetRender(bool render) = 0;
+ // Starts or stops transmission (and potentially capture) of local video.
+ virtual bool SetSend(bool send) = 0;
+ // Adds a new receive-only stream with the specified SSRC.
+ virtual bool AddStream(uint32 ssrc, uint32 voice_ssrc) = 0;
+ // Removes a stream added with AddStream.
+ virtual bool RemoveStream(uint32 ssrc) = 0;
+ // Sets the renderer object to be used for the specified stream.
+ // If SSRC is 0, the renderer is used for the 'default' stream.
+ virtual bool SetRenderer(uint32 ssrc, VideoRenderer* renderer) = 0;
+ // Gets quality stats for the channel.
+ virtual bool GetStats(VideoMediaInfo* info) = 0;
+
+ // Send an intra frame to the receivers.
+ virtual bool SendIntraFrame() = 0;
+ // Reuqest each of the remote senders to send an intra frame.
+ virtual bool RequestIntraFrame() = 0;
+
+ sigslot::signal2<uint32, Error> SignalMediaError;
+
+ protected:
+ VideoRenderer *renderer_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIACHANNEL_H_
diff --git a/talk/session/phone/mediaengine.cc b/talk/session/phone/mediaengine.cc
new file mode 100644
index 0000000..42b0954
--- /dev/null
+++ b/talk/session/phone/mediaengine.cc
@@ -0,0 +1,40 @@
+//
+// libjingle
+// Copyright 2004--2007, 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/session/phone/mediaengine.h"
+
+
+namespace cricket {
+
+// TODO: according to thaloun, HAVE_GIPSVIDEO will always
+// be false, so we can get rid of it.
+
+MediaEngine* MediaEngine::Create() {
+ return new NullMediaEngine();
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/mediaengine.h b/talk/session/phone/mediaengine.h
new file mode 100644
index 0000000..234a668
--- /dev/null
+++ b/talk/session/phone/mediaengine.h
@@ -0,0 +1,337 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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_MEDIAENGINE_H_
+#define TALK_SESSION_PHONE_MEDIAENGINE_H_
+
+#ifdef OSX
+#include <CoreAudio/CoreAudio.h>
+#endif
+
+#include <string>
+#include <vector>
+
+#include "talk/base/sigslotrepeater.h"
+#include "talk/session/phone/codec.h"
+#include "talk/session/phone/devicemanager.h"
+#include "talk/session/phone/mediachannel.h"
+#include "talk/session/phone/videocommon.h"
+
+namespace cricket {
+
+// A class for playing out soundclips.
+class SoundclipMedia {
+ public:
+ enum SoundclipFlags {
+ SF_LOOP = 1,
+ };
+
+ virtual ~SoundclipMedia() {}
+
+ // Plays a sound out to the speakers with the given audio stream. The stream
+ // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+ // on this SoundclipMedia, it is stopped. If clip is NULL, nothing is played.
+ // Returns whether it was successful.
+ virtual bool PlaySound(const char *clip, int len, int flags) = 0;
+};
+
+// MediaEngine is an abstraction of a media engine which can be subclassed
+// to support different media componentry backends. It supports voice and
+// video operations in the same class to facilitate proper synchronization
+// between both media types.
+class MediaEngine {
+ public:
+ // TODO: Move this to a global location (also used in DeviceManager)
+ // Capabilities of the media engine.
+ enum Capabilities {
+ AUDIO_RECV = 1 << 0,
+ AUDIO_SEND = 1 << 1,
+ VIDEO_RECV = 1 << 2,
+ VIDEO_SEND = 1 << 3,
+ };
+
+ // Bitmask flags for options that may be supported by the media engine
+ // implementation
+ enum AudioOptions {
+ ECHO_CANCELLATION = 1 << 0,
+ AUTO_GAIN_CONTROL = 1 << 1,
+ DEFAULT_AUDIO_OPTIONS = ECHO_CANCELLATION | AUTO_GAIN_CONTROL
+ };
+ enum VideoOptions {
+ };
+
+ virtual ~MediaEngine() {}
+ static MediaEngine* Create();
+
+ // Initialization
+ // Starts the engine.
+ virtual bool Init() = 0;
+ // Shuts down the engine.
+ virtual void Terminate() = 0;
+ // Returns what the engine is capable of, as a set of Capabilities, above.
+ virtual int GetCapabilities() = 0;
+
+ // MediaChannel creation
+ // Creates a voice media channel. Returns NULL on failure.
+ virtual VoiceMediaChannel *CreateChannel() = 0;
+ // Creates a video media channel, paired with the specified voice channel.
+ // Returns NULL on failure.
+ virtual VideoMediaChannel *CreateVideoChannel(
+ VoiceMediaChannel* voice_media_channel) = 0;
+
+ // Creates a soundclip object for playing sounds on. Returns NULL on failure.
+ virtual SoundclipMedia *CreateSoundclip() = 0;
+
+ // Configuration
+ // Sets global audio options. "options" are from AudioOptions, above.
+ virtual bool SetAudioOptions(int options) = 0;
+ // Sets global video options. "options" are from VideoOptions, above.
+ virtual bool SetVideoOptions(int options) = 0;
+ // Sets the default (maximum) codec/resolution and encoder option to capture
+ // and encode video.
+ virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config)
+ = 0;
+
+ // Device selection
+ // TODO: Add method for selecting the soundclip device.
+ virtual bool SetSoundDevices(const Device* in_device,
+ const Device* out_device) = 0;
+ virtual bool SetVideoCaptureDevice(const Device* cam_device) = 0;
+
+ // Device configuration
+ // Sets the current speaker volume, as a value between 0 and 255.
+ virtual bool SetOutputVolume(int level) = 0;
+
+ // Local monitoring
+ // Gets the current microphone level, as a value between 0 and 10.
+ virtual int GetInputLevel() = 0;
+ // Starts or stops the local microphone. Useful if local mic info is needed
+ // prior to a call being connected; the mic will be started automatically
+ // when a VoiceMediaChannel starts sending.
+ virtual bool SetLocalMonitor(bool enable) = 0;
+ // Installs a callback for raw frames from the local camera.
+ virtual bool SetLocalRenderer(VideoRenderer* renderer) = 0;
+ // Starts/stops local camera.
+ virtual CaptureResult SetVideoCapture(bool capture) = 0;
+
+ virtual const std::vector<AudioCodec>& audio_codecs() = 0;
+ virtual const std::vector<VideoCodec>& video_codecs() = 0;
+ virtual bool FindAudioCodec(const AudioCodec &codec) = 0;
+ virtual bool FindVideoCodec(const VideoCodec &codec) = 0;
+
+ // Logging control
+ virtual void SetVoiceLogging(int min_sev, const char* filter) = 0;
+ virtual void SetVideoLogging(int min_sev, const char* filter) = 0;
+
+ sigslot::repeater1<CaptureResult> SignalVideoCaptureResult;
+};
+
+// CompositeMediaEngine constructs a MediaEngine from separate
+// voice and video engine classes.
+template<class VOICE, class VIDEO>
+class CompositeMediaEngine : public MediaEngine {
+ public:
+ CompositeMediaEngine() {}
+ virtual bool Init() {
+ if (!voice_.Init())
+ return false;
+ if (!video_.Init()) {
+ voice_.Terminate();
+ return false;
+ }
+ SignalVideoCaptureResult.repeat(video_.SignalCaptureResult);
+ return true;
+ }
+ virtual void Terminate() {
+ video_.Terminate();
+ voice_.Terminate();
+ }
+
+ virtual int GetCapabilities() {
+ return (voice_.GetCapabilities() | video_.GetCapabilities());
+ }
+ virtual VoiceMediaChannel *CreateChannel() {
+ return voice_.CreateChannel();
+ }
+ virtual VideoMediaChannel *CreateVideoChannel(VoiceMediaChannel* channel) {
+ return video_.CreateChannel(channel);
+ }
+ virtual SoundclipMedia *CreateSoundclip() {
+ return voice_.CreateSoundclip();
+ }
+
+ virtual bool SetAudioOptions(int o) {
+ return voice_.SetOptions(o);
+ }
+ virtual bool SetVideoOptions(int o) {
+ return video_.SetOptions(o);
+ }
+ virtual bool SetDefaultVideoEncoderConfig(const VideoEncoderConfig& config) {
+ return video_.SetDefaultEncoderConfig(config);
+ }
+
+ virtual bool SetSoundDevices(const Device* in_device,
+ const Device* out_device) {
+ return voice_.SetDevices(in_device, out_device);
+ }
+ virtual bool SetVideoCaptureDevice(const Device* cam_device) {
+ return video_.SetCaptureDevice(cam_device);
+ }
+
+ virtual bool SetOutputVolume(int level) {
+ return voice_.SetOutputVolume(level);
+ }
+
+ virtual int GetInputLevel() {
+ return voice_.GetInputLevel();
+ }
+ virtual bool SetLocalMonitor(bool enable) {
+ return voice_.SetLocalMonitor(enable);
+ }
+ virtual bool SetLocalRenderer(VideoRenderer* renderer) {
+ return video_.SetLocalRenderer(renderer);
+ }
+ virtual CaptureResult SetVideoCapture(bool capture) {
+ return video_.SetCapture(capture);
+ }
+
+ virtual const std::vector<AudioCodec>& audio_codecs() {
+ return voice_.codecs();
+ }
+ virtual const std::vector<VideoCodec>& video_codecs() {
+ return video_.codecs();
+ }
+
+ virtual bool FindAudioCodec(const AudioCodec &codec) {
+ return voice_.FindCodec(codec);
+ }
+ virtual bool FindVideoCodec(const VideoCodec &codec) {
+ return video_.FindCodec(codec);
+ }
+
+ virtual void SetVoiceLogging(int min_sev, const char* filter) {
+ return voice_.SetLogging(min_sev, filter);
+ }
+ virtual void SetVideoLogging(int min_sev, const char* filter) {
+ return video_.SetLogging(min_sev, filter);
+ }
+
+ private:
+ VOICE voice_;
+ VIDEO video_;
+};
+
+class NullVoiceMediaChannel : public VoiceMediaChannel {
+ public:
+ explicit NullVoiceMediaChannel() {}
+ ~NullVoiceMediaChannel() {}
+ // MediaChannel implementations
+ virtual void OnPacketReceived(talk_base::Buffer* packet) {}
+ virtual void OnRtcpReceived(talk_base::Buffer* packet) {}
+ virtual void SetSendSsrc(uint32 id) {}
+ virtual bool SetRtcpCName(const std::string& cname) { return true; }
+ virtual bool Mute(bool on) { return true; }
+ virtual bool SetSendBandwidth(bool autobw, int bps) { return true; }
+ virtual bool SetOptions(int options) { return true; }
+ // VoiceMediaChannel implementations
+ virtual bool SetRecvCodecs(const std::vector<AudioCodec> &codecs) {
+ return true;
+ }
+ virtual bool SetSendCodecs(const std::vector<AudioCodec> &codecs) {
+ return true;
+ }
+ virtual bool SetPlayout(bool playout) { return true; }
+ virtual bool SetSend(SendFlags flag) { return true; }
+ virtual bool AddStream(uint32 ssrc) { return true; }
+ virtual bool RemoveStream(uint32 ssrc) { return true; }
+ virtual bool GetActiveStreams(AudioInfo::StreamList* streams) { return true; }
+ virtual int GetOutputLevel() { return 0; }
+ virtual void SetRingbackTone(const char *buf, int len) {}
+ virtual bool PlayRingbackTone(bool play, bool loop) { return true; }
+ virtual bool PressDTMF(int event, bool playout) { return true; }
+ virtual bool GetStats(VoiceMediaInfo* info) { return false; }
+};
+
+// NullVoiceEngine can be used with CompositeMediaEngine in the case where only
+// a video engine is desired.
+class NullVoiceEngine {
+ public:
+ bool Init() { return true; }
+ void Terminate() {}
+ int GetCapabilities() { return 0; }
+ VoiceMediaChannel* CreateChannel() {
+ // TODO: See if we can make things work without requiring
+ // allocation of a channel.
+ return new NullVoiceMediaChannel();
+ }
+ SoundclipMedia* CreateSoundclip() {
+ return NULL;
+ }
+ bool SetOptions(int opts) { return true; }
+ bool SetDevices(const Device* in_device, const Device* out_device) {
+ return true;
+ }
+ bool SetOutputVolume(int level) { return true; }
+ int GetInputLevel() { return 0; }
+ bool SetLocalMonitor(bool enable) { return true; }
+ const std::vector<AudioCodec>& codecs() { return codecs_; }
+ bool FindCodec(const AudioCodec&) { return false; }
+ void SetLogging(int min_sev, const char* filter) {}
+ private:
+ std::vector<AudioCodec> codecs_;
+};
+
+// NullVideoEngine can be used with CompositeMediaEngine in the case where only
+// a voice engine is desired.
+class NullVideoEngine {
+ public:
+ bool Init() { return true; }
+ void Terminate() {}
+ int GetCapabilities() { return 0; }
+ VideoMediaChannel* CreateChannel(VoiceMediaChannel* voice_media_channel) {
+ return NULL;
+ }
+ bool SetOptions(int opts) { return true; }
+ bool SetDefaultEncoderConfig(const VideoEncoderConfig& config) {
+ return true;
+ }
+ bool SetCaptureDevice(const Device* cam_device) { return true; }
+ bool SetLocalRenderer(VideoRenderer* renderer) { return true; }
+ CaptureResult SetCapture(bool capture) { return CR_SUCCESS; }
+ const std::vector<VideoCodec>& codecs() { return codecs_; }
+ bool FindCodec(const VideoCodec&) { return false; }
+ void SetLogging(int min_sev, const char* filter) {}
+ sigslot::signal1<CaptureResult> SignalCaptureResult;
+ private:
+ std::vector<VideoCodec> codecs_;
+};
+
+typedef CompositeMediaEngine<NullVoiceEngine, NullVideoEngine> NullMediaEngine;
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIAENGINE_H_
diff --git a/talk/session/phone/mediamonitor.cc b/talk/session/phone/mediamonitor.cc
new file mode 100644
index 0000000..909b536
--- /dev/null
+++ b/talk/session/phone/mediamonitor.cc
@@ -0,0 +1,109 @@
+/*
+ * libjingle
+ * Copyright 2005--2007, 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/session/phone/mediamonitor.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace cricket {
+
+enum {
+ MSG_MONITOR_POLL = 1,
+ MSG_MONITOR_START = 2,
+ MSG_MONITOR_STOP = 3,
+ MSG_MONITOR_SIGNAL = 4
+};
+
+MediaMonitor::MediaMonitor(talk_base::Thread* worker_thread,
+ talk_base::Thread* monitor_thread)
+ : worker_thread_(worker_thread),
+ monitor_thread_(monitor_thread), monitoring_(false), rate_(0) {
+}
+
+MediaMonitor::~MediaMonitor() {
+ monitoring_ = false;
+ monitor_thread_->Clear(this);
+ worker_thread_->Clear(this);
+}
+
+void MediaMonitor::Start(uint32 milliseconds) {
+ rate_ = milliseconds;
+ if (rate_ < 100)
+ rate_ = 100;
+ worker_thread_->Post(this, MSG_MONITOR_START);
+}
+
+void MediaMonitor::Stop() {
+ worker_thread_->Post(this, MSG_MONITOR_STOP);
+ rate_ = 0;
+}
+
+void MediaMonitor::OnMessage(talk_base::Message* message) {
+ talk_base::CritScope cs(&crit_);
+
+ switch (message->message_id) {
+ case MSG_MONITOR_START:
+ ASSERT(talk_base::Thread::Current() == worker_thread_);
+ if (!monitoring_) {
+ monitoring_ = true;
+ PollMediaChannel();
+ }
+ break;
+
+ case MSG_MONITOR_STOP:
+ ASSERT(talk_base::Thread::Current() == worker_thread_);
+ if (monitoring_) {
+ monitoring_ = false;
+ worker_thread_->Clear(this);
+ }
+ break;
+
+ case MSG_MONITOR_POLL:
+ ASSERT(talk_base::Thread::Current() == worker_thread_);
+ PollMediaChannel();
+ break;
+
+ case MSG_MONITOR_SIGNAL:
+ ASSERT(talk_base::Thread::Current() == monitor_thread_);
+ Update();
+ break;
+ }
+}
+
+void MediaMonitor::PollMediaChannel() {
+ talk_base::CritScope cs(&crit_);
+ ASSERT(talk_base::Thread::Current() == worker_thread_);
+
+ GetStats();
+
+ // Signal the monitoring thread, start another poll timer
+ monitor_thread_->Post(this, MSG_MONITOR_SIGNAL);
+ worker_thread_->PostDelayed(rate_, this, MSG_MONITOR_POLL);
+}
+
+}
diff --git a/talk/session/phone/mediamonitor.h b/talk/session/phone/mediamonitor.h
new file mode 100644
index 0000000..6b964aa
--- /dev/null
+++ b/talk/session/phone/mediamonitor.h
@@ -0,0 +1,98 @@
+/*
+ * libjingle
+ * Copyright 2005--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Class to collect statistics from a media channel
+
+#ifndef TALK_SESSION_PHONE_MEDIAMONITOR_H_
+#define TALK_SESSION_PHONE_MEDIAMONITOR_H_
+
+#include "talk/base/thread.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/criticalsection.h"
+#include "talk/session/phone/mediachannel.h"
+
+namespace cricket {
+
+// The base MediaMonitor class, independent of voice and video.
+class MediaMonitor : public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+ public:
+ MediaMonitor(talk_base::Thread* worker_thread,
+ talk_base::Thread* monitor_thread);
+ ~MediaMonitor();
+
+ void Start(uint32 milliseconds);
+ void Stop();
+
+ protected:
+ void OnMessage(talk_base::Message *message);
+ void PollMediaChannel();
+ virtual void GetStats() = 0;
+ virtual void Update() = 0;
+
+ talk_base::CriticalSection crit_;
+ talk_base::Thread* worker_thread_;
+ talk_base::Thread* monitor_thread_;
+ bool monitoring_;
+ uint32 rate_;
+};
+
+// Templatized MediaMonitor that can deal with different kinds of media.
+template<class MC, class MI>
+class MediaMonitorT : public MediaMonitor {
+ public:
+ MediaMonitorT(MC* media_channel, talk_base::Thread* worker_thread,
+ talk_base::Thread* monitor_thread)
+ : MediaMonitor(worker_thread, monitor_thread),
+ media_channel_(media_channel) {}
+ sigslot::signal2<MC*, const MI&> SignalUpdate;
+
+ protected:
+ // These routines assume the crit_ lock is held by the calling thread.
+ virtual void GetStats() {
+ media_info_.Clear();
+ media_channel_->GetStats(&media_info_);
+ }
+ virtual void Update() {
+ MI stats(media_info_);
+ crit_.Leave();
+ SignalUpdate(media_channel_, stats);
+ crit_.Enter();
+ }
+
+ private:
+ MC* media_channel_;
+ MI media_info_;
+};
+
+typedef MediaMonitorT<VoiceMediaChannel, VoiceMediaInfo> VoiceMediaMonitor;
+typedef MediaMonitorT<VideoMediaChannel, VideoMediaInfo> VideoMediaMonitor;
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIAMONITOR_H_
+
diff --git a/talk/session/phone/mediasessionclient.cc b/talk/session/phone/mediasessionclient.cc
new file mode 100644
index 0000000..c862cd5
--- /dev/null
+++ b/talk/session/phone/mediasessionclient.cc
@@ -0,0 +1,1020 @@
+/*
+ * 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 <string>
+
+#include "talk/session/phone/mediasessionclient.h"
+
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/stringencode.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/parsing.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/session/phone/srtpfilter.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlconstants.h"
+
+using namespace talk_base;
+
+namespace {
+const std::string kInline = "inline:";
+}
+
+namespace cricket {
+
+typedef std::vector<CryptoParams> CryptoParamsVec;
+
+MediaSessionClient::MediaSessionClient(
+ const buzz::Jid& jid, SessionManager *manager)
+ : jid_(jid), session_manager_(manager), focus_call_(NULL),
+ channel_manager_(new ChannelManager(session_manager_->worker_thread())),
+ secure_(SEC_DISABLED) {
+ Construct();
+}
+
+MediaSessionClient::MediaSessionClient(
+ const buzz::Jid& jid, SessionManager *manager,
+ MediaEngine* media_engine, DeviceManager* device_manager)
+ : jid_(jid), session_manager_(manager), focus_call_(NULL),
+ channel_manager_(new ChannelManager(
+ media_engine, device_manager, session_manager_->worker_thread())),
+ secure_(SEC_DISABLED) {
+ Construct();
+}
+
+
+void MediaSessionClient::Construct() {
+ // Register ourselves as the handler of phone and video sessions.
+ session_manager_->AddClient(NS_JINGLE_RTP, this);
+ // Forward device notifications.
+ SignalDevicesChange.repeat(channel_manager_->SignalDevicesChange);
+ // Bring up the channel manager.
+ // In previous versions of ChannelManager, this was done automatically
+ // in the constructor.
+ channel_manager_->Init();
+}
+
+MediaSessionClient::~MediaSessionClient() {
+ // Destroy all calls
+ std::map<uint32, Call *>::iterator it;
+ while (calls_.begin() != calls_.end()) {
+ std::map<uint32, Call *>::iterator it = calls_.begin();
+ DestroyCall((*it).second);
+ }
+
+ // Delete channel manager. This will wait for the channels to exit
+ delete channel_manager_;
+
+ // Remove ourselves from the client map.
+ session_manager_->RemoveClient(NS_JINGLE_RTP);
+}
+
+bool CreateCryptoParams(int tag, const std::string& cipher, CryptoParams *out) {
+ std::string key;
+ key.reserve(SRTP_MASTER_KEY_BASE64_LEN);
+
+ if (!CreateRandomString(SRTP_MASTER_KEY_BASE64_LEN, &key)) {
+ return false;
+ }
+ out->tag = tag;
+ out->cipher_suite = cipher;
+ out->key_params = kInline + key;
+ return true;
+}
+
+bool AddCryptoParams(const std::string& cipher_suite, CryptoParamsVec *out) {
+ int size = out->size();
+
+ out->resize(size + 1);
+ return CreateCryptoParams(size, cipher_suite, &out->at(size));
+}
+
+// For audio, HMAC 32 is prefered because of the low overhead.
+bool GetSupportedAudioCryptos(CryptoParamsVec* cryptos) {
+#ifdef HAVE_SRTP
+ return AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_32, cryptos) &&
+ AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_80, cryptos);
+#else
+ return false;
+#endif
+}
+
+bool GetSupportedVideoCryptos(CryptoParamsVec* cryptos) {
+#ifdef HAVE_SRTP
+ return AddCryptoParams(CS_AES_CM_128_HMAC_SHA1_80, cryptos);
+#else
+ return false;
+#endif
+}
+
+SessionDescription* MediaSessionClient::CreateOffer(
+ const CallOptions& options) {
+ SessionDescription* offer = new SessionDescription();
+ AudioContentDescription* audio = new AudioContentDescription();
+
+
+ AudioCodecs audio_codecs;
+ channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+ for (AudioCodecs::const_iterator codec = audio_codecs.begin();
+ codec != audio_codecs.end(); ++codec) {
+ audio->AddCodec(*codec);
+ }
+ if (options.is_muc) {
+ audio->set_ssrc(0);
+ }
+ audio->SortCodecs();
+
+ if (secure() != SEC_DISABLED) {
+ CryptoParamsVec audio_cryptos;
+ if (GetSupportedAudioCryptos(&audio_cryptos)) {
+ for (CryptoParamsVec::const_iterator crypto = audio_cryptos.begin();
+ crypto != audio_cryptos.end(); ++crypto) {
+ audio->AddCrypto(*crypto);
+ }
+ }
+ if (secure() == SEC_REQUIRED) {
+ if (audio->cryptos().empty()) {
+ return NULL; // Abort, crypto required but none found.
+ }
+ audio->set_crypto_required(true);
+ }
+ }
+
+ offer->AddContent(CN_AUDIO, NS_JINGLE_RTP, audio);
+
+ // add video codecs, if this is a video call
+ if (options.is_video) {
+ VideoContentDescription* video = new VideoContentDescription();
+ VideoCodecs video_codecs;
+ channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+ for (VideoCodecs::const_iterator codec = video_codecs.begin();
+ codec != video_codecs.end(); ++codec) {
+ video->AddCodec(*codec);
+ }
+ if (options.is_muc) {
+ video->set_ssrc(0);
+ }
+ video->set_bandwidth(options.video_bandwidth);
+ video->SortCodecs();
+
+ if (secure() != SEC_DISABLED) {
+ CryptoParamsVec video_cryptos;
+ if (GetSupportedVideoCryptos(&video_cryptos)) {
+ for (CryptoParamsVec::const_iterator crypto = video_cryptos.begin();
+ crypto != video_cryptos.end(); ++crypto) {
+ video->AddCrypto(*crypto);
+ }
+ }
+ if (secure() == SEC_REQUIRED) {
+ if (video->cryptos().empty()) {
+ return NULL; // Abort, crypto required but none found.
+ }
+ video->set_crypto_required(true);
+ }
+ }
+
+ offer->AddContent(CN_VIDEO, NS_JINGLE_RTP, video);
+ }
+
+ return offer;
+}
+
+const ContentInfo* GetFirstMediaContent(const SessionDescription* sdesc,
+ MediaType media_type) {
+ if (sdesc == NULL)
+ return NULL;
+
+ const ContentInfos& contents = sdesc->contents();
+ for (ContentInfos::const_iterator content = contents.begin();
+ content != contents.end(); content++) {
+ if (content->type == NS_JINGLE_RTP) {
+ const MediaContentDescription* media =
+ static_cast<const MediaContentDescription*>(content->description);
+ if (media->type() == media_type) {
+ return &*content;
+ }
+ }
+ }
+ return NULL;
+}
+
+const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc) {
+ return GetFirstMediaContent(sdesc, MEDIA_TYPE_AUDIO);
+}
+
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc) {
+ return GetFirstMediaContent(sdesc, MEDIA_TYPE_VIDEO);
+}
+
+// For video support only 80-bit SHA1 HMAC. For audio 32-bit HMAC is
+// tolerated because it is low overhead. Pick the crypto in the list
+// that is supported.
+bool SelectCrypto(const MediaContentDescription* offer, CryptoParams *crypto) {
+ bool audio = offer->type() == MEDIA_TYPE_AUDIO;
+ const CryptoParamsVec& cryptos = offer->cryptos();
+
+ for (CryptoParamsVec::const_iterator i = cryptos.begin();
+ i != cryptos.end(); ++i) {
+ if (CS_AES_CM_128_HMAC_SHA1_80 == i->cipher_suite ||
+ (CS_AES_CM_128_HMAC_SHA1_32 == i->cipher_suite && audio)) {
+ return CreateCryptoParams(i->tag, i->cipher_suite, crypto);
+ }
+ }
+ return false;
+}
+
+SessionDescription* MediaSessionClient::CreateAnswer(
+ const SessionDescription* offer, const CallOptions& options) {
+ // The answer contains the intersection of the codecs in the offer with the
+ // codecs we support, ordered by our local preference. As indicated by
+ // XEP-0167, we retain the same payload ids from the offer in the answer.
+ SessionDescription* accept = new SessionDescription();
+
+ const ContentInfo* audio_content = GetFirstAudioContent(offer);
+ if (audio_content) {
+ const AudioContentDescription* audio_offer =
+ static_cast<const AudioContentDescription*>(audio_content->description);
+ AudioContentDescription* audio_accept = new AudioContentDescription();
+ AudioCodecs audio_codecs;
+ channel_manager_->GetSupportedAudioCodecs(&audio_codecs);
+ for (AudioCodecs::const_iterator ours = audio_codecs.begin();
+ ours != audio_codecs.end(); ++ours) {
+ for (AudioCodecs::const_iterator theirs = audio_offer->codecs().begin();
+ theirs != audio_offer->codecs().end(); ++theirs) {
+ if (ours->Matches(*theirs)) {
+ AudioCodec negotiated(*ours);
+ negotiated.id = theirs->id;
+ audio_accept->AddCodec(negotiated);
+ }
+ }
+ }
+
+ audio_accept->SortCodecs();
+
+ if (secure() != SEC_DISABLED) {
+ CryptoParams crypto;
+
+ if (SelectCrypto(audio_offer, &crypto)) {
+ audio_accept->AddCrypto(crypto);
+ }
+ }
+
+ if (audio_accept->cryptos().empty() &&
+ (audio_offer->crypto_required() || secure() == SEC_REQUIRED)) {
+ return NULL; // Fails the session setup.
+ }
+ accept->AddContent(audio_content->name, audio_content->type, audio_accept);
+ }
+
+ const ContentInfo* video_content = GetFirstVideoContent(offer);
+ if (video_content) {
+ const VideoContentDescription* video_offer =
+ static_cast<const VideoContentDescription*>(video_content->description);
+ VideoContentDescription* video_accept = new VideoContentDescription();
+ VideoCodecs video_codecs;
+ channel_manager_->GetSupportedVideoCodecs(&video_codecs);
+ for (VideoCodecs::const_iterator ours = video_codecs.begin();
+ ours != video_codecs.end(); ++ours) {
+ for (VideoCodecs::const_iterator theirs = video_offer->codecs().begin();
+ theirs != video_offer->codecs().end(); ++theirs) {
+ if (ours->Matches(*theirs)) {
+ VideoCodec negotiated(*ours);
+ negotiated.id = theirs->id;
+ video_accept->AddCodec(negotiated);
+ }
+ }
+ }
+
+ video_accept->set_bandwidth(options.video_bandwidth);
+ video_accept->SortCodecs();
+
+ if (secure() != SEC_DISABLED) {
+ CryptoParams crypto;
+
+ if (SelectCrypto(video_offer, &crypto)) {
+ video_accept->AddCrypto(crypto);
+ }
+ }
+
+ if (video_accept->cryptos().empty() &&
+ (video_offer->crypto_required() || secure() == SEC_REQUIRED)) {
+ return NULL; // Fails the session setup.
+ }
+ accept->AddContent(video_content->name, video_content->type, video_accept);
+ }
+
+ return accept;
+}
+
+Call *MediaSessionClient::CreateCall() {
+ Call *call = new Call(this);
+ calls_[call->id()] = call;
+ SignalCallCreate(call);
+ return call;
+}
+
+void MediaSessionClient::OnSessionCreate(Session *session,
+ bool received_initiate) {
+ if (received_initiate) {
+ session->SignalState.connect(this, &MediaSessionClient::OnSessionState);
+ }
+}
+
+void MediaSessionClient::OnSessionState(BaseSession* base_session,
+ BaseSession::State state) {
+ // MediaSessionClient can only be used with a Session*, so it's
+ // safe to cast here.
+ Session* session = static_cast<Session*>(base_session);
+
+ if (state == Session::STATE_RECEIVEDINITIATE) {
+ // The creation of the call must happen after the session has
+ // processed the initiate message because we need the
+ // remote_description to know what content names to use in the
+ // call.
+
+ // If our accept would have no codecs, then we must reject this call.
+ const SessionDescription* offer = session->remote_description();
+ const SessionDescription* accept = CreateAnswer(offer, CallOptions());
+ const ContentInfo* audio_content = GetFirstAudioContent(accept);
+ const AudioContentDescription* audio_accept = (!audio_content) ? NULL :
+ static_cast<const AudioContentDescription*>(audio_content->description);
+
+ // For some reason, we need to create the call even when we
+ // reject.
+ Call *call = CreateCall();
+ session_map_[session->id()] = call;
+ call->IncomingSession(session, offer);
+
+ if (!audio_accept || audio_accept->codecs().size() == 0) {
+ session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+ }
+ delete accept;
+ }
+}
+
+void MediaSessionClient::DestroyCall(Call *call) {
+ // Change focus away, signal destruction
+
+ if (call == focus_call_)
+ SetFocus(NULL);
+ SignalCallDestroy(call);
+
+ // Remove it from calls_ map and delete
+
+ std::map<uint32, Call *>::iterator it = calls_.find(call->id());
+ if (it != calls_.end())
+ calls_.erase(it);
+
+ delete call;
+}
+
+void MediaSessionClient::OnSessionDestroy(Session *session) {
+ // Find the call this session is in, remove it
+
+ std::map<std::string, Call *>::iterator it = session_map_.find(session->id());
+ ASSERT(it != session_map_.end());
+ if (it != session_map_.end()) {
+ Call *call = (*it).second;
+ session_map_.erase(it);
+ call->RemoveSession(session);
+ }
+}
+
+Call *MediaSessionClient::GetFocus() {
+ return focus_call_;
+}
+
+void MediaSessionClient::SetFocus(Call *call) {
+ Call *old_focus_call = focus_call_;
+ if (focus_call_ != call) {
+ if (focus_call_ != NULL)
+ focus_call_->EnableChannels(false);
+ focus_call_ = call;
+ if (focus_call_ != NULL)
+ focus_call_->EnableChannels(true);
+ SignalFocus(focus_call_, old_focus_call);
+ }
+}
+
+void MediaSessionClient::JoinCalls(Call *call_to_join, Call *call) {
+ // Move all sessions from call to call_to_join, delete call.
+ // If call_to_join has focus, added sessions should have enabled channels.
+
+ if (focus_call_ == call)
+ SetFocus(NULL);
+ call_to_join->Join(call, focus_call_ == call_to_join);
+ DestroyCall(call);
+}
+
+Session *MediaSessionClient::CreateSession(Call *call) {
+ const std::string& type = NS_JINGLE_RTP;
+ Session *session = session_manager_->CreateSession(jid().Str(), type);
+ session_map_[session->id()] = call;
+ return session;
+}
+
+bool ParseGingleAudioCodec(const buzz::XmlElement* element, AudioCodec* out) {
+ int id = GetXmlAttr(element, QN_ID, -1);
+ if (id < 0)
+ return false;
+
+ std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+ int clockrate = GetXmlAttr(element, QN_CLOCKRATE, 0);
+ int bitrate = GetXmlAttr(element, QN_BITRATE, 0);
+ int channels = GetXmlAttr(element, QN_CHANNELS, 1);
+ *out = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+ return true;
+}
+
+bool ParseGingleVideoCodec(const buzz::XmlElement* element, VideoCodec* out) {
+ int id = GetXmlAttr(element, QN_ID, -1);
+ if (id < 0)
+ return false;
+
+ std::string name = GetXmlAttr(element, QN_NAME, buzz::STR_EMPTY);
+ int width = GetXmlAttr(element, QN_WIDTH, 0);
+ int height = GetXmlAttr(element, QN_HEIGHT, 0);
+ int framerate = GetXmlAttr(element, QN_FRAMERATE, 0);
+
+ *out = VideoCodec(id, name, width, height, framerate, 0);
+ return true;
+}
+
+void ParseGingleSsrc(const buzz::XmlElement* parent_elem,
+ const buzz::QName& name,
+ MediaContentDescription* content) {
+ const buzz::XmlElement* ssrc_elem = parent_elem->FirstNamed(name);
+ if (ssrc_elem) {
+ content->set_ssrc(strtoul(ssrc_elem->BodyText().c_str(), NULL, 10));
+ }
+}
+
+bool ParseCryptoParams(const buzz::XmlElement* element,
+ CryptoParams* out,
+ ParseError* error) {
+ if (!element->HasAttr(QN_CRYPTO_SUITE)) {
+ return BadParse("crypto: crypto-suite attribute missing ", error);
+ } else if (!element->HasAttr(QN_CRYPTO_KEY_PARAMS)) {
+ return BadParse("crypto: key-params attribute missing ", error);
+ } else if (!element->HasAttr(QN_CRYPTO_TAG)) {
+ return BadParse("crypto: tag attribute missing ", error);
+ }
+
+ const std::string& crypto_suite = element->Attr(QN_CRYPTO_SUITE);
+ const std::string& key_params = element->Attr(QN_CRYPTO_KEY_PARAMS);
+ const int tag = GetXmlAttr(element, QN_CRYPTO_TAG, 0);
+ const std::string& session_params =
+ element->Attr(QN_CRYPTO_SESSION_PARAMS); // Optional.
+
+ *out = CryptoParams(tag, crypto_suite, key_params, session_params);
+ return true;
+}
+
+
+// Parse the first encryption element found with a matching 'usage'
+// element.
+// <usage/> is specific to Gingle. In Jingle, <crypto/> is already
+// scoped to a content.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseGingleEncryption(const buzz::XmlElement* desc,
+ const buzz::QName& usage,
+ MediaContentDescription* media,
+ ParseError* error) {
+ for (const buzz::XmlElement* encryption = desc->FirstNamed(QN_ENCRYPTION);
+ encryption != NULL;
+ encryption = encryption->NextNamed(QN_ENCRYPTION)) {
+ if (encryption->FirstNamed(usage) != NULL) {
+ media->set_crypto_required(
+ GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+ for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+ crypto != NULL;
+ crypto = crypto->NextNamed(QN_CRYPTO)) {
+ CryptoParams params;
+ if (!ParseCryptoParams(crypto, ¶ms, error)) {
+ return false;
+ }
+ media->AddCrypto(params);
+ }
+ break;
+ }
+ }
+ return true;
+}
+
+void ParseBandwidth(const buzz::XmlElement* parent_elem,
+ MediaContentDescription* media) {
+ const buzz::XmlElement* bw_elem = GetXmlChild(parent_elem, LN_BANDWIDTH);
+ int bandwidth_kbps;
+ if (bw_elem && FromString(bw_elem->BodyText(), &bandwidth_kbps)) {
+ if (bandwidth_kbps >= 0) {
+ media->set_bandwidth(bandwidth_kbps * 1000);
+ }
+ }
+}
+
+bool ParseGingleAudioContent(const buzz::XmlElement* content_elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ AudioContentDescription* audio = new AudioContentDescription();
+
+ if (content_elem->FirstElement()) {
+ for (const buzz::XmlElement* codec_elem =
+ content_elem->FirstNamed(QN_GINGLE_AUDIO_PAYLOADTYPE);
+ codec_elem != NULL;
+ codec_elem = codec_elem->NextNamed(QN_GINGLE_AUDIO_PAYLOADTYPE)) {
+ AudioCodec codec;
+ if (ParseGingleAudioCodec(codec_elem, &codec)) {
+ audio->AddCodec(codec);
+ }
+ }
+ } else {
+ // For backward compatibility, we can assume the other client is
+ // an old version of Talk if it has no audio payload types at all.
+ audio->AddCodec(AudioCodec(103, "ISAC", 16000, -1, 1, 1));
+ audio->AddCodec(AudioCodec(0, "PCMU", 8000, 64000, 1, 0));
+ }
+
+ ParseGingleSsrc(content_elem, QN_GINGLE_AUDIO_SRCID, audio);
+
+ if (!ParseGingleEncryption(content_elem, QN_GINGLE_AUDIO_CRYPTO_USAGE,
+ audio, error)) {
+ return false;
+ }
+
+ *content = audio;
+ return true;
+}
+
+bool ParseGingleVideoContent(const buzz::XmlElement* content_elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ VideoContentDescription* video = new VideoContentDescription();
+
+ for (const buzz::XmlElement* codec_elem =
+ content_elem->FirstNamed(QN_GINGLE_VIDEO_PAYLOADTYPE);
+ codec_elem != NULL;
+ codec_elem = codec_elem->NextNamed(QN_GINGLE_VIDEO_PAYLOADTYPE)) {
+ VideoCodec codec;
+ if (ParseGingleVideoCodec(codec_elem, &codec)) {
+ video->AddCodec(codec);
+ }
+ }
+
+ ParseGingleSsrc(content_elem, QN_GINGLE_VIDEO_SRCID, video);
+ ParseBandwidth(content_elem, video);
+
+ if (!ParseGingleEncryption(content_elem, QN_GINGLE_VIDEO_CRYPTO_USAGE,
+ video, error)) {
+ return false;
+ }
+
+ *content = video;
+ return true;
+}
+
+void ParsePayloadTypeParameters(const buzz::XmlElement* element,
+ std::map<std::string, std::string>* paramap) {
+ for (const buzz::XmlElement* param = element->FirstNamed(QN_PARAMETER);
+ param != NULL; param = param->NextNamed(QN_PARAMETER)) {
+ std::string name = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_NAME,
+ buzz::STR_EMPTY);
+ std::string value = GetXmlAttr(param, QN_PAYLOADTYPE_PARAMETER_VALUE,
+ buzz::STR_EMPTY);
+ if (!name.empty() && !value.empty()) {
+ paramap->insert(make_pair(name, value));
+ }
+ }
+}
+
+int FindWithDefault(const std::map<std::string, std::string>& map,
+ const std::string& key, const int def) {
+ std::map<std::string, std::string>::const_iterator iter = map.find(key);
+ return (iter == map.end()) ? def : atoi(iter->second.c_str());
+}
+
+
+// Parse the first encryption element found.
+// Return false if there was an encryption element and it could not be
+// parsed.
+bool ParseJingleEncryption(const buzz::XmlElement* content_elem,
+ MediaContentDescription* media,
+ ParseError* error) {
+ const buzz::XmlElement* encryption =
+ content_elem->FirstNamed(QN_ENCRYPTION);
+ if (encryption == NULL) {
+ return true;
+ }
+
+ media->set_crypto_required(
+ GetXmlAttr(encryption, QN_ENCRYPTION_REQUIRED, false));
+
+ for (const buzz::XmlElement* crypto = encryption->FirstNamed(QN_CRYPTO);
+ crypto != NULL;
+ crypto = crypto->NextNamed(QN_CRYPTO)) {
+ CryptoParams params;
+ if (!ParseCryptoParams(crypto, ¶ms, error)) {
+ return false;
+ }
+ media->AddCrypto(params);
+ }
+ return true;
+}
+
+bool ParseJingleAudioCodec(const buzz::XmlElement* elem, AudioCodec* codec) {
+ int id = GetXmlAttr(elem, QN_ID, -1);
+ if (id < 0)
+ return false;
+
+ std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+ int clockrate = GetXmlAttr(elem, QN_CLOCKRATE, 0);
+ int channels = GetXmlAttr(elem, QN_CHANNELS, 1);
+
+ std::map<std::string, std::string> paramap;
+ ParsePayloadTypeParameters(elem, ¶map);
+ int bitrate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_BITRATE, 0);
+
+ *codec = AudioCodec(id, name, clockrate, bitrate, channels, 0);
+ return true;
+}
+
+bool ParseJingleVideoCodec(const buzz::XmlElement* elem, VideoCodec* codec) {
+ int id = GetXmlAttr(elem, QN_ID, -1);
+ if (id < 0)
+ return false;
+
+ std::string name = GetXmlAttr(elem, QN_NAME, buzz::STR_EMPTY);
+
+ std::map<std::string, std::string> paramap;
+ ParsePayloadTypeParameters(elem, ¶map);
+ int width = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_WIDTH, 0);
+ int height = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_HEIGHT, 0);
+ int framerate = FindWithDefault(paramap, PAYLOADTYPE_PARAMETER_FRAMERATE, 0);
+
+ *codec = VideoCodec(id, name, width, height, framerate, 0);
+ return true;
+}
+
+bool ParseJingleAudioContent(const buzz::XmlElement* content_elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ AudioContentDescription* audio = new AudioContentDescription();
+
+ for (const buzz::XmlElement* payload_elem =
+ content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+ payload_elem != NULL;
+ payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+ AudioCodec codec;
+ if (ParseJingleAudioCodec(payload_elem, &codec)) {
+ audio->AddCodec(codec);
+ }
+ }
+
+ if (!ParseJingleEncryption(content_elem, audio, error)) {
+ return false;
+ }
+ // TODO: Figure out how to integrate SSRC into Jingle.
+ *content = audio;
+ return true;
+}
+
+bool ParseJingleVideoContent(const buzz::XmlElement* content_elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ VideoContentDescription* video = new VideoContentDescription();
+
+ for (const buzz::XmlElement* payload_elem =
+ content_elem->FirstNamed(QN_JINGLE_RTP_PAYLOADTYPE);
+ payload_elem != NULL;
+ payload_elem = payload_elem->NextNamed(QN_JINGLE_RTP_PAYLOADTYPE)) {
+ VideoCodec codec;
+ if (ParseJingleVideoCodec(payload_elem, &codec)) {
+ video->AddCodec(codec);
+ }
+ }
+
+ ParseBandwidth(content_elem, video);
+
+ if (!ParseJingleEncryption(content_elem, video, error)) {
+ return false;
+ }
+ // TODO: Figure out how to integrate SSRC into Jingle.
+ *content = video;
+ return true;
+}
+
+bool MediaSessionClient::ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* content_elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ if (protocol == PROTOCOL_GINGLE) {
+ const std::string& content_type = content_elem->Name().Namespace();
+ if (NS_GINGLE_AUDIO == content_type) {
+ return ParseGingleAudioContent(content_elem, content, error);
+ } else if (NS_GINGLE_VIDEO == content_type) {
+ return ParseGingleVideoContent(content_elem, content, error);
+ } else {
+ return BadParse("Unknown content type: " + content_type, error);
+ }
+ } else {
+ std::string media;
+ if (!RequireXmlAttr(content_elem, QN_JINGLE_CONTENT_MEDIA, &media, error))
+ return false;
+
+ if (media == JINGLE_CONTENT_MEDIA_AUDIO) {
+ return ParseJingleAudioContent(content_elem, content, error);
+ } else if (media == JINGLE_CONTENT_MEDIA_VIDEO) {
+ return ParseJingleVideoContent(content_elem, content, error);
+ } else {
+ return BadParse("Unknown media: " + media, error);
+ }
+ }
+}
+
+buzz::XmlElement* CreateGingleAudioCodecElem(const AudioCodec& codec) {
+ buzz::XmlElement* payload_type =
+ new buzz::XmlElement(QN_GINGLE_AUDIO_PAYLOADTYPE, true);
+ AddXmlAttr(payload_type, QN_ID, codec.id);
+ payload_type->AddAttr(QN_NAME, codec.name);
+ if (codec.clockrate > 0)
+ AddXmlAttr(payload_type, QN_CLOCKRATE, codec.clockrate);
+ if (codec.bitrate > 0)
+ AddXmlAttr(payload_type, QN_BITRATE, codec.bitrate);
+ if (codec.channels > 1)
+ AddXmlAttr(payload_type, QN_CHANNELS, codec.channels);
+ return payload_type;
+}
+
+buzz::XmlElement* CreateGingleVideoCodecElem(const VideoCodec& codec) {
+ buzz::XmlElement* payload_type =
+ new buzz::XmlElement(QN_GINGLE_VIDEO_PAYLOADTYPE, true);
+ AddXmlAttr(payload_type, QN_ID, codec.id);
+ payload_type->AddAttr(QN_NAME, codec.name);
+ AddXmlAttr(payload_type, QN_WIDTH, codec.width);
+ AddXmlAttr(payload_type, QN_HEIGHT, codec.height);
+ AddXmlAttr(payload_type, QN_FRAMERATE, codec.framerate);
+ return payload_type;
+}
+
+buzz::XmlElement* CreateGingleSsrcElem(const buzz::QName& name, uint32 ssrc) {
+ buzz::XmlElement* elem = new buzz::XmlElement(name, true);
+ if (ssrc) {
+ SetXmlBody(elem, ssrc);
+ }
+ return elem;
+}
+
+buzz::XmlElement* CreateBandwidthElem(const buzz::QName& name, int bps) {
+ int kbps = bps / 1000;
+ buzz::XmlElement* elem = new buzz::XmlElement(name);
+ elem->AddAttr(buzz::QN_TYPE, "AS");
+ SetXmlBody(elem, kbps);
+ return elem;
+}
+
+// For Jingle, usage_qname is empty.
+buzz::XmlElement* CreateJingleEncryptionElem(const CryptoParamsVec& cryptos,
+ bool required) {
+ buzz::XmlElement* encryption_elem = new buzz::XmlElement(QN_ENCRYPTION);
+
+ if (required) {
+ encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+ }
+
+ for (CryptoParamsVec::const_iterator i = cryptos.begin();
+ i != cryptos.end();
+ ++i) {
+ buzz::XmlElement* crypto_elem = new buzz::XmlElement(QN_CRYPTO);
+
+ AddXmlAttr(crypto_elem, QN_CRYPTO_TAG, i->tag);
+ crypto_elem->AddAttr(QN_CRYPTO_SUITE, i->cipher_suite);
+ crypto_elem->AddAttr(QN_CRYPTO_KEY_PARAMS, i->key_params);
+ if (!i->session_params.empty()) {
+ crypto_elem->AddAttr(QN_CRYPTO_SESSION_PARAMS, i->session_params);
+ }
+ encryption_elem->AddElement(crypto_elem);
+ }
+ return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleEncryptionElem(const CryptoParamsVec& cryptos,
+ const buzz::QName& usage_qname,
+ bool required) {
+ buzz::XmlElement* encryption_elem =
+ CreateJingleEncryptionElem(cryptos, required);
+
+ if (required) {
+ encryption_elem->SetAttr(QN_ENCRYPTION_REQUIRED, "true");
+ }
+
+ buzz::XmlElement* usage_elem = new buzz::XmlElement(usage_qname);
+ encryption_elem->AddElement(usage_elem);
+
+ return encryption_elem;
+}
+
+buzz::XmlElement* CreateGingleAudioContentElem(
+ const AudioContentDescription* audio,
+ bool crypto_required) {
+ buzz::XmlElement* elem =
+ new buzz::XmlElement(QN_GINGLE_AUDIO_CONTENT, true);
+
+ for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+ codec != audio->codecs().end(); ++codec) {
+ elem->AddElement(CreateGingleAudioCodecElem(*codec));
+ }
+ if (audio->ssrc_set()) {
+ elem->AddElement(CreateGingleSsrcElem(
+ QN_GINGLE_AUDIO_SRCID, audio->ssrc()));
+ }
+
+ const CryptoParamsVec& cryptos = audio->cryptos();
+ if (!cryptos.empty()) {
+ elem->AddElement(CreateGingleEncryptionElem(cryptos,
+ QN_GINGLE_AUDIO_CRYPTO_USAGE,
+ crypto_required));
+ }
+
+
+ return elem;
+}
+
+buzz::XmlElement* CreateGingleVideoContentElem(
+ const VideoContentDescription* video,
+ bool crypto_required) {
+ buzz::XmlElement* elem =
+ new buzz::XmlElement(QN_GINGLE_VIDEO_CONTENT, true);
+
+ for (VideoCodecs::const_iterator codec = video->codecs().begin();
+ codec != video->codecs().end(); ++codec) {
+ elem->AddElement(CreateGingleVideoCodecElem(*codec));
+ }
+ if (video->ssrc_set()) {
+ elem->AddElement(CreateGingleSsrcElem(
+ QN_GINGLE_VIDEO_SRCID, video->ssrc()));
+ }
+ if (video->bandwidth() != kAutoBandwidth) {
+ elem->AddElement(CreateBandwidthElem(QN_GINGLE_VIDEO_BANDWIDTH,
+ video->bandwidth()));
+ }
+
+ const CryptoParamsVec& cryptos = video->cryptos();
+ if (!cryptos.empty()) {
+ elem->AddElement(CreateGingleEncryptionElem(cryptos,
+ QN_GINGLE_VIDEO_CRYPTO_USAGE,
+ crypto_required));
+ }
+
+ return elem;
+}
+
+buzz::XmlElement* CreatePayloadTypeParameterElem(
+ const std::string& name, int value) {
+ buzz::XmlElement* elem = new buzz::XmlElement(QN_PARAMETER);
+
+ elem->AddAttr(QN_PAYLOADTYPE_PARAMETER_NAME, name);
+ AddXmlAttr(elem, QN_PAYLOADTYPE_PARAMETER_VALUE, value);
+
+ return elem;
+}
+
+buzz::XmlElement* CreateJingleAudioCodecElem(const AudioCodec& codec) {
+ buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+ AddXmlAttr(elem, QN_ID, codec.id);
+ elem->AddAttr(QN_NAME, codec.name);
+ if (codec.clockrate > 0) {
+ AddXmlAttr(elem, QN_CLOCKRATE, codec.clockrate);
+ }
+ if (codec.bitrate > 0) {
+ elem->AddElement(CreatePayloadTypeParameterElem(
+ PAYLOADTYPE_PARAMETER_BITRATE, codec.bitrate));
+ }
+ if (codec.channels > 1) {
+ AddXmlAttr(elem, QN_CHANNELS, codec.channels);
+ }
+
+ return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoCodecElem(const VideoCodec& codec) {
+ buzz::XmlElement* elem = new buzz::XmlElement(QN_JINGLE_RTP_PAYLOADTYPE);
+
+ AddXmlAttr(elem, QN_ID, codec.id);
+ elem->AddAttr(QN_NAME, codec.name);
+ elem->AddElement(CreatePayloadTypeParameterElem(
+ PAYLOADTYPE_PARAMETER_WIDTH, codec.width));
+ elem->AddElement(CreatePayloadTypeParameterElem(
+ PAYLOADTYPE_PARAMETER_HEIGHT, codec.height));
+ elem->AddElement(CreatePayloadTypeParameterElem(
+ PAYLOADTYPE_PARAMETER_FRAMERATE, codec.framerate));
+
+ return elem;
+}
+
+buzz::XmlElement* CreateJingleAudioContentElem(
+ const AudioContentDescription* audio, bool crypto_required) {
+ buzz::XmlElement* elem =
+ new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+ elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_AUDIO);
+
+ for (AudioCodecs::const_iterator codec = audio->codecs().begin();
+ codec != audio->codecs().end(); ++codec) {
+ elem->AddElement(CreateJingleAudioCodecElem(*codec));
+ }
+
+ const CryptoParamsVec& cryptos = audio->cryptos();
+ if (!cryptos.empty()) {
+ elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+ }
+
+ // TODO: Figure out how to integrate SSRC into Jingle.
+ return elem;
+}
+
+buzz::XmlElement* CreateJingleVideoContentElem(
+ const VideoContentDescription* video, bool crypto_required) {
+ buzz::XmlElement* elem =
+ new buzz::XmlElement(QN_JINGLE_RTP_CONTENT, true);
+
+ elem->SetAttr(QN_JINGLE_CONTENT_MEDIA, JINGLE_CONTENT_MEDIA_VIDEO);
+
+ for (VideoCodecs::const_iterator codec = video->codecs().begin();
+ codec != video->codecs().end(); ++codec) {
+ elem->AddElement(CreateJingleVideoCodecElem(*codec));
+ }
+
+ const CryptoParamsVec& cryptos = video->cryptos();
+ if (!cryptos.empty()) {
+ elem->AddElement(CreateJingleEncryptionElem(cryptos, crypto_required));
+ }
+
+ if (video->bandwidth() != kAutoBandwidth) {
+ elem->AddElement(CreateBandwidthElem(QN_JINGLE_RTP_BANDWIDTH,
+ video->bandwidth()));
+ }
+
+ // TODO: Figure out how to integrate SSRC into Jingle.
+ return elem;
+}
+
+bool MediaSessionClient::WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error) {
+ const MediaContentDescription* media =
+ static_cast<const MediaContentDescription*>(content);
+ bool crypto_required = secure() == SEC_REQUIRED;
+
+ if (media->type() == MEDIA_TYPE_AUDIO) {
+ const AudioContentDescription* audio =
+ static_cast<const AudioContentDescription*>(media);
+ if (protocol == PROTOCOL_GINGLE) {
+ *elem = CreateGingleAudioContentElem(audio, crypto_required);
+ } else {
+ *elem = CreateJingleAudioContentElem(audio, crypto_required);
+ }
+ } else if (media->type() == MEDIA_TYPE_VIDEO) {
+ const VideoContentDescription* video =
+ static_cast<const VideoContentDescription*>(media);
+ if (protocol == PROTOCOL_GINGLE) {
+ *elem = CreateGingleVideoContentElem(video, crypto_required);
+ } else {
+ *elem = CreateJingleVideoContentElem(video, crypto_required);
+ }
+ } else {
+ return BadWrite("Unknown content type: " + media->type(), error);
+ }
+
+ return true;
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/mediasessionclient.h b/talk/session/phone/mediasessionclient.h
new file mode 100644
index 0000000..a74fe3e
--- /dev/null
+++ b/talk/session/phone/mediasessionclient.h
@@ -0,0 +1,274 @@
+/*
+ * 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_SESSION_PHONE_MEDIASESSIONCLIENT_H_
+#define TALK_SESSION_PHONE_MEDIASESSIONCLIENT_H_
+
+#include <string>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include "talk/session/phone/call.h"
+#include "talk/session/phone/channelmanager.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/base/sigslot.h"
+#include "talk/base/sigslotrepeater.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/thread.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+class Call;
+class SessionDescription;
+typedef std::vector<AudioCodec> AudioCodecs;
+typedef std::vector<VideoCodec> VideoCodecs;
+
+// SEC_ENABLED and SEC_REQUIRED should only be used if the session
+// was negotiated over TLS, to protect the inline crypto material
+// exchange.
+// SEC_DISABLED: No crypto in outgoing offer and answer. Fail any
+// offer with crypto required.
+// SEC_ENABLED: Crypto in outgoing offer and answer. Fail any offer
+// with unsupported required crypto. Crypto set but not
+// required in outgoing offer.
+// SEC_REQUIRED: Crypto in outgoing offer and answer with
+// required='true'. Fail any offer with no or
+// unsupported crypto (implicit crypto required='true'
+// in the offer.)
+enum SecureMediaPolicy {SEC_DISABLED, SEC_ENABLED, SEC_REQUIRED};
+
+const int kAutoBandwidth = -1;
+
+struct CallOptions {
+ CallOptions() :
+ is_video(false),
+ is_muc(false),
+ video_bandwidth(kAutoBandwidth) {
+ }
+
+ bool is_video;
+ bool is_muc;
+ // bps. -1 == auto.
+ int video_bandwidth;
+};
+
+class MediaSessionClient: public SessionClient, public sigslot::has_slots<> {
+ public:
+
+ MediaSessionClient(const buzz::Jid& jid, SessionManager *manager);
+ // Alternative constructor, allowing injection of media_engine
+ // and device_manager.
+ MediaSessionClient(const buzz::Jid& jid, SessionManager *manager,
+ MediaEngine* media_engine, DeviceManager* device_manager);
+ ~MediaSessionClient();
+
+ const buzz::Jid &jid() const { return jid_; }
+ SessionManager* session_manager() const { return session_manager_; }
+ ChannelManager* channel_manager() const { return channel_manager_; }
+
+ int GetCapabilities() { return channel_manager_->GetCapabilities(); }
+
+ Call *CreateCall();
+ void DestroyCall(Call *call);
+
+ Call *GetFocus();
+ void SetFocus(Call *call);
+
+ void JoinCalls(Call *call_to_join, Call *call);
+
+ bool GetAudioInputDevices(std::vector<std::string>* names) {
+ return channel_manager_->GetAudioInputDevices(names);
+ }
+ bool GetAudioOutputDevices(std::vector<std::string>* names) {
+ return channel_manager_->GetAudioOutputDevices(names);
+ }
+ bool GetVideoCaptureDevices(std::vector<std::string>* names) {
+ return channel_manager_->GetVideoCaptureDevices(names);
+ }
+
+ bool SetAudioOptions(const std::string& in_name, const std::string& out_name,
+ int opts) {
+ return channel_manager_->SetAudioOptions(in_name, out_name, opts);
+ }
+ bool SetOutputVolume(int level) {
+ return channel_manager_->SetOutputVolume(level);
+ }
+ bool SetVideoOptions(const std::string& cam_device) {
+ return channel_manager_->SetVideoOptions(cam_device);
+ }
+
+ sigslot::signal2<Call *, Call *> SignalFocus;
+ sigslot::signal1<Call *> SignalCallCreate;
+ sigslot::signal1<Call *> SignalCallDestroy;
+ sigslot::repeater0<> SignalDevicesChange;
+
+ SessionDescription* CreateOffer(const CallOptions& options);
+ SessionDescription* CreateAnswer(const SessionDescription* offer,
+ const CallOptions& options);
+
+ SecureMediaPolicy secure() const { return secure_; }
+ void set_secure(SecureMediaPolicy s) { secure_ = s; }
+
+ private:
+ void Construct();
+ void OnSessionCreate(Session *session, bool received_initiate);
+ void OnSessionState(BaseSession *session, BaseSession::State state);
+ void OnSessionDestroy(Session *session);
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error);
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error);
+ Session *CreateSession(Call *call);
+
+ buzz::Jid jid_;
+ SessionManager* session_manager_;
+ Call *focus_call_;
+ ChannelManager *channel_manager_;
+ std::map<uint32, Call *> calls_;
+ std::map<std::string, Call *> session_map_;
+ SecureMediaPolicy secure_;
+ friend class Call;
+};
+
+enum MediaType {
+ MEDIA_TYPE_AUDIO,
+ MEDIA_TYPE_VIDEO
+};
+
+class MediaContentDescription : public ContentDescription {
+ public:
+ MediaContentDescription()
+ : ssrc_(0),
+ ssrc_set_(false),
+ rtcp_mux_(false),
+ rtp_headers_disabled_(false),
+ crypto_required_(false),
+ bandwidth_(kAutoBandwidth) {
+ }
+
+ virtual MediaType type() const = 0;
+
+ uint32 ssrc() const { return ssrc_; }
+ bool ssrc_set() const { return ssrc_set_; }
+ void set_ssrc(uint32 ssrc) {
+ ssrc_ = ssrc;
+ ssrc_set_ = true;
+ }
+
+ bool rtcp_mux() const { return rtcp_mux_; }
+ void set_rtcp_mux(bool mux) { rtcp_mux_ = mux; }
+
+ bool rtp_headers_disabled() const {
+ return rtp_headers_disabled_;
+ }
+ void set_rtp_headers_disabled(bool disable) {
+ rtp_headers_disabled_ = disable;
+ }
+
+ const std::vector<CryptoParams>& cryptos() const { return cryptos_; }
+ void AddCrypto(const CryptoParams& params) {
+ cryptos_.push_back(params);
+ }
+ bool crypto_required() const { return crypto_required_; }
+ void set_crypto_required(bool crypto) {
+ crypto_required_ = crypto;
+ }
+
+ int bandwidth() const { return bandwidth_; }
+ void set_bandwidth(int bandwidth) { bandwidth_ = bandwidth; }
+
+ protected:
+ uint32 ssrc_;
+ bool ssrc_set_;
+ bool rtcp_mux_;
+ bool rtp_headers_disabled_;
+ std::vector<CryptoParams> cryptos_;
+ bool crypto_required_;
+ int bandwidth_;
+};
+
+template <class C>
+class MediaContentDescriptionImpl : public MediaContentDescription {
+ public:
+ struct PreferenceSort {
+ bool operator()(C a, C b) { return a.preference > b.preference; }
+ };
+
+ const std::vector<C>& codecs() const { return codecs_; }
+ void AddCodec(const C& codec) {
+ codecs_.push_back(codec);
+ }
+ void SortCodecs() {
+ std::sort(codecs_.begin(), codecs_.end(), PreferenceSort());
+ }
+
+ private:
+ std::vector<C> codecs_;
+};
+
+class AudioContentDescription : public MediaContentDescriptionImpl<AudioCodec> {
+ public:
+ AudioContentDescription() :
+ conference_mode_(false) {}
+
+ virtual MediaType type() const { return MEDIA_TYPE_AUDIO; }
+
+ bool conference_mode() const { return conference_mode_; }
+ void set_conference_mode(bool enable) {
+ conference_mode_ = enable;
+ }
+
+ const std::string &lang() const { return lang_; }
+ void set_lang(const std::string &lang) { lang_ = lang; }
+
+
+ private:
+ bool conference_mode_;
+ std::string lang_;
+};
+
+class VideoContentDescription : public MediaContentDescriptionImpl<VideoCodec> {
+ public:
+ virtual MediaType type() const { return MEDIA_TYPE_VIDEO; }
+};
+
+// Convenience functions.
+const ContentInfo* GetFirstAudioContent(const SessionDescription* sdesc);
+const ContentInfo* GetFirstVideoContent(const SessionDescription* sdesc);
+
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIASESSIONCLIENT_H_
diff --git a/talk/session/phone/mediasink.h b/talk/session/phone/mediasink.h
new file mode 100644
index 0000000..078b534
--- /dev/null
+++ b/talk/session/phone/mediasink.h
@@ -0,0 +1,49 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_MEDIASINK_H_
+#define TALK_SESSION_PHONE_MEDIASINK_H_
+
+namespace cricket {
+
+// MediaSinkInterface is a sink to handle RTP and RTCP packets that are sent or
+// received by a channel. Each channel needs two MediaSinkInterface, one for
+// the sent packets and the other for the received packets.
+class MediaSinkInterface {
+ public:
+ virtual ~MediaSinkInterface() {}
+
+ virtual void SetMaxSize(size_t size) = 0;
+ virtual bool Enable(bool enable) = 0;
+ virtual bool IsEnabled() const = 0;
+ virtual void OnRtpPacket(const void* data, size_t size) = 0;
+ virtual void OnRtcpPacket(const void* data, size_t size) = 0;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_MEDIASINK_H_
diff --git a/talk/session/phone/rtcpmuxfilter.cc b/talk/session/phone/rtcpmuxfilter.cc
new file mode 100644
index 0000000..8654214
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter.cc
@@ -0,0 +1,92 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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/session/phone/rtcpmuxfilter.h"
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+RtcpMuxFilter::RtcpMuxFilter() : state_(ST_INIT), offer_enable_(false) {
+}
+
+bool RtcpMuxFilter::IsActive() const {
+ // We can receive muxed media prior to the accept, so we have to be able to
+ // deal with that.
+ return (state_ == ST_SENTOFFER || state_ == ST_ACTIVE);
+}
+
+bool RtcpMuxFilter::SetOffer(bool offer_enable, ContentSource source) {
+ bool ret = false;
+ if (state_ == ST_INIT) {
+ offer_enable_ = offer_enable;
+ state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+ ret = true;
+ } else {
+ LOG(LS_ERROR) << "Invalid state for RTCP mux offer";
+ }
+ return ret;
+}
+
+bool RtcpMuxFilter::SetAnswer(bool answer_enable, ContentSource source) {
+ bool ret = false;
+ if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+ (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+ if (offer_enable_) {
+ state_ = (answer_enable) ? ST_ACTIVE : ST_INIT;
+ ret = true;
+ } else {
+ // If the offer didn't specify RTCP mux, the answer shouldn't either.
+ if (!answer_enable) {
+ ret = true;
+ state_ = ST_INIT;
+ } else {
+ LOG(LS_WARNING) << "Invalid parameters in RTCP mux answer";
+ }
+ }
+ } else {
+ LOG(LS_ERROR) << "Invalid state for RTCP mux answer";
+ }
+ return ret;
+}
+
+bool RtcpMuxFilter::DemuxRtcp(const char* data, int len) {
+ // If we're muxing RTP/RTCP, we must inspect each packet delivered and
+ // determine whether it is RTP or RTCP. We do so by checking the packet type,
+ // and assuming RTP if type is 0-63 or 96-127. For additional details, see
+ // http://tools.ietf.org/html/rfc5761.
+ // Note that if we offer RTCP mux, we may receive muxed RTCP before we
+ // receive the answer, so we operate in that state too.
+ if (!IsActive()) {
+ return false;
+ }
+
+ int type = (len >= 2) ? (static_cast<uint8>(data[1]) & 0x7F) : 0;
+ return (type >= 64 && type < 96);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/rtcpmuxfilter.h b/talk/session/phone/rtcpmuxfilter.h
new file mode 100644
index 0000000..0224e9f
--- /dev/null
+++ b/talk/session/phone/rtcpmuxfilter.h
@@ -0,0 +1,61 @@
+/*
+ * libjingle
+ * Copyright 2004--2010, 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_RTCPMUXFILTER_H_
+#define TALK_SESSION_PHONE_RTCPMUXFILTER_H_
+
+#include "talk/base/basictypes.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+namespace cricket {
+
+// RTCP Muxer, as defined in RFC 5761 (http://tools.ietf.org/html/rfc5761)
+class RtcpMuxFilter {
+ public:
+ RtcpMuxFilter();
+
+ // Whether the filter is active, i.e. has RTCP mux been properly negotiated.
+ bool IsActive() const;
+
+ // Specifies whether the offer indicates the use of RTCP mux.
+ bool SetOffer(bool offer_enable, ContentSource src);
+
+ // Specifies whether the answer indicates the use of RTCP mux.
+ bool SetAnswer(bool answer_enable, ContentSource src);
+
+ // Determines whether the specified packet is RTCP.
+ bool DemuxRtcp(const char* data, int len);
+
+ private:
+ enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+ State state_;
+ bool offer_enable_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_RTCPMUXFILTER_H_
diff --git a/talk/session/phone/rtpdump.cc b/talk/session/phone/rtpdump.cc
new file mode 100644
index 0000000..37d664b
--- /dev/null
+++ b/talk/session/phone/rtpdump.cc
@@ -0,0 +1,336 @@
+/*
+ * libjingle
+ * Copyright 2010, 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/session/phone/rtpdump.h"
+
+#include <string>
+
+#include "talk/base/bytebuffer.h"
+#include "talk/base/byteorder.h"
+#include "talk/base/logging.h"
+#include "talk/base/time.h"
+
+namespace cricket {
+
+const std::string RtpDumpFileHeader::kFirstLine =
+ "#!rtpplay1.0 0.0.0.0/0\n";
+
+RtpDumpFileHeader::RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p)
+ : start_sec(start_ms / 1000),
+ start_usec(start_ms % 1000 * 1000),
+ source(s),
+ port(p),
+ padding(0) {
+}
+
+void RtpDumpFileHeader::WriteToByteBuffer(talk_base::ByteBuffer* buf) {
+ buf->WriteUInt32(start_sec);
+ buf->WriteUInt32(start_usec);
+ buf->WriteUInt32(source);
+ buf->WriteUInt16(port);
+ buf->WriteUInt16(padding);
+}
+
+// RTP packet format (http://www.networksorcery.com/enp/protocol/rtp.htm).
+static const size_t kMinimumRtpHeaderSize = 12;
+static const uint32 kDefaultTimeIncrease = 30;
+
+bool RtpDumpPacket::IsValidRtpPacket() const {
+ return !is_rtcp && data.size() >= kMinimumRtpHeaderSize;
+}
+
+bool RtpDumpPacket::GetRtpSeqNum(uint16* seq_num) const {
+ if (!seq_num || !IsValidRtpPacket()) {
+ return false;
+ }
+ *seq_num = talk_base::GetBE16(&data[2]);
+ return true;
+}
+
+bool RtpDumpPacket::GetRtpTimestamp(uint32* ts) const {
+ if (!ts || !IsValidRtpPacket()) {
+ return false;
+ }
+ *ts = talk_base::GetBE32(&data[4]);
+ return true;
+}
+
+bool RtpDumpPacket::GetRtpSsrc(uint32* ssrc) const {
+ if (!ssrc || !IsValidRtpPacket()) {
+ return false;
+ }
+ *ssrc = talk_base::GetBE32(&data[8]);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpReader.
+///////////////////////////////////////////////////////////////////////////
+talk_base::StreamResult RtpDumpReader::ReadPacket(RtpDumpPacket* packet) {
+ if (!packet) return talk_base::SR_ERROR;
+
+ talk_base::StreamResult res = talk_base::SR_SUCCESS;
+ // Read the file header if it has not been read yet.
+ if (!file_header_read_) {
+ res = ReadFileHeader();
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+ file_header_read_ = true;
+ }
+
+ // Read the RTP dump packet header.
+ char header[RtpDumpPacket::kHeaderLength];
+ res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+ talk_base::ByteBuffer buf(header, sizeof(header));
+ uint16 dump_packet_len;
+ uint16 data_len;
+ buf.ReadUInt16(&dump_packet_len);
+ buf.ReadUInt16(&data_len); // data.size() for RTP, 0 for RTCP.
+ packet->is_rtcp = (0 == data_len);
+ buf.ReadUInt32(&packet->elapsed_time);
+ packet->data.resize(dump_packet_len - sizeof(header));
+
+ // Read the actual RTP or RTCP packet.
+ return stream_->ReadAll(&packet->data[0], packet->data.size(), NULL, NULL);
+}
+
+talk_base::StreamResult RtpDumpReader::ReadFileHeader() {
+ // Read the first line.
+ std::string first_line;
+ talk_base::StreamResult res = stream_->ReadLine(&first_line);
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+ if (!CheckFirstLine(first_line)) {
+ return talk_base::SR_ERROR;
+ }
+
+ // Read the 16 byte file header.
+ char header[RtpDumpFileHeader::kHeaderLength];
+ res = stream_->ReadAll(header, sizeof(header), NULL, NULL);
+ if (res == talk_base::SR_SUCCESS) {
+ talk_base::ByteBuffer buf(header, sizeof(header));
+ uint32 start_sec;
+ uint32 start_usec;
+ buf.ReadUInt32(&start_sec);
+ buf.ReadUInt32(&start_usec);
+ start_time_ms_ = start_sec * 1000 + start_usec / 1000;
+ // Increase the length by 1 since first_line does not contain the ending \n.
+ first_line_and_file_header_len_ = first_line.size() + 1 + sizeof(header);
+ }
+ return res;
+}
+
+bool RtpDumpReader::CheckFirstLine(const std::string& first_line) {
+ // The first line is like "#!rtpplay1.0 address/port"
+ bool matched = (0 == first_line.find("#!rtpplay1.0 "));
+
+ // The address could be IP or hostname. We do not check it here. Instead, we
+ // check the port at the end.
+ size_t pos = first_line.find('/');
+ matched &= (pos != std::string::npos && pos < first_line.size() - 1);
+ for (++pos; pos < first_line.size() && matched; ++pos) {
+ matched &= (0 != isdigit(first_line[pos]));
+ }
+
+ return matched;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpLoopReader.
+///////////////////////////////////////////////////////////////////////////
+RtpDumpLoopReader::RtpDumpLoopReader(talk_base::StreamInterface* stream)
+ : RtpDumpReader(stream),
+ loop_count_(0),
+ elapsed_time_increases_(0),
+ rtp_seq_num_increase_(0),
+ rtp_timestamp_increase_(0),
+ packet_count_(0),
+ frame_count_(0),
+ first_elapsed_time_(0),
+ first_rtp_seq_num_(0),
+ first_rtp_timestamp_(0),
+ prev_elapsed_time_(0),
+ prev_rtp_seq_num_(0),
+ prev_rtp_timestamp_(0) {
+}
+
+talk_base::StreamResult RtpDumpLoopReader::ReadPacket(RtpDumpPacket* packet) {
+ if (!packet) return talk_base::SR_ERROR;
+
+ talk_base::StreamResult res = RtpDumpReader::ReadPacket(packet);
+ if (talk_base::SR_SUCCESS == res) {
+ if (0 == loop_count_) {
+ // During the first loop, we update the statistics of the input stream.
+ UpdateStreamStatistics(*packet);
+ }
+ } else if (talk_base::SR_EOS == res) {
+ if (0 == loop_count_) {
+ // At the end of the first loop, calculate elapsed_time_increases_,
+ // rtp_seq_num_increase_, and rtp_timestamp_increase_, which will be
+ // used during the second and later loops.
+ CalculateIncreases();
+ }
+
+ // Rewind the input stream to the first dump packet and read again.
+ ++loop_count_;
+ if (RewindToFirstDumpPacket()) {
+ res = RtpDumpReader::ReadPacket(packet);
+ }
+ }
+
+ if (talk_base::SR_SUCCESS == res && loop_count_ > 0) {
+ // During the second and later loops, we update the elapsed time of the dump
+ // packet. If the dumped packet is a RTP packet, we also update its RTP
+ // sequence number and timestamp.
+ UpdateDumpPacket(packet);
+ }
+
+ return res;
+}
+
+void RtpDumpLoopReader::UpdateStreamStatistics(const RtpDumpPacket& packet) {
+ // Get the RTP sequence number and timestamp of the dump packet.
+ uint16 rtp_seq_num = 0;
+ packet.GetRtpSeqNum(&rtp_seq_num);
+ uint32 rtp_timestamp = 0;
+ packet.GetRtpTimestamp(&rtp_timestamp);
+
+ // Set the timestamps and sequence number for the first dump packet.
+ if (0 == packet_count_++) {
+ first_elapsed_time_ = packet.elapsed_time;
+ first_rtp_seq_num_ = rtp_seq_num;
+ first_rtp_timestamp_ = rtp_timestamp;
+ // The first packet belongs to a new payload frame.
+ ++frame_count_;
+ } else if (rtp_timestamp != prev_rtp_timestamp_) {
+ // The current and previous packets belong to different payload frames.
+ ++frame_count_;
+ }
+
+ prev_elapsed_time_ = packet.elapsed_time;
+ prev_rtp_timestamp_ = rtp_timestamp;
+ prev_rtp_seq_num_ = rtp_seq_num;
+}
+
+void RtpDumpLoopReader::CalculateIncreases() {
+ // At this time, prev_elapsed_time_, prev_rtp_seq_num_, and
+ // prev_rtp_timestamp_ are values of the last dump packet in the input stream.
+ rtp_seq_num_increase_ = prev_rtp_seq_num_ - first_rtp_seq_num_ + 1;
+ // If we have only one packet or frame, we use the default timestamp
+ // increase. Otherwise, we use the difference between the first and the last
+ // packets or frames.
+ elapsed_time_increases_ = packet_count_ <= 1 ? kDefaultTimeIncrease :
+ (prev_elapsed_time_ - first_elapsed_time_) * packet_count_ /
+ (packet_count_ - 1);
+ rtp_timestamp_increase_ = frame_count_ <= 1 ? kDefaultTimeIncrease :
+ (prev_rtp_timestamp_ - first_rtp_timestamp_) * frame_count_ /
+ (frame_count_ - 1);
+}
+
+void RtpDumpLoopReader::UpdateDumpPacket(RtpDumpPacket* packet) {
+ // Increase the elapsed time of the dump packet.
+ packet->elapsed_time += loop_count_ * elapsed_time_increases_;
+
+ if (packet->IsValidRtpPacket()) {
+ // Get the old RTP sequence number and timestamp.
+ uint16 sequence = 0;
+ packet->GetRtpSeqNum(&sequence);
+ uint32 timestamp = 0;
+ packet->GetRtpTimestamp(×tamp);
+ // Increase the RTP sequence number and timestamp.
+ sequence += loop_count_ * rtp_seq_num_increase_;
+ timestamp += loop_count_ * rtp_timestamp_increase_;
+ // Write the updated sequence number and timestamp back to the RTP packet.
+ talk_base::ByteBuffer buffer;
+ buffer.WriteUInt16(sequence);
+ buffer.WriteUInt32(timestamp);
+ memcpy(&packet->data[2], buffer.Data(), buffer.Length());
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+// Implementation of RtpDumpWriter.
+///////////////////////////////////////////////////////////////////////////
+
+RtpDumpWriter::RtpDumpWriter(talk_base::StreamInterface* stream)
+ : stream_(stream),
+ file_header_written_(false),
+ start_time_ms_(talk_base::Time()) {
+ }
+
+uint32 RtpDumpWriter::GetElapsedTime() const {
+ return talk_base::TimeSince(start_time_ms_);
+}
+
+talk_base::StreamResult RtpDumpWriter::WritePacket(
+ const void* data, size_t data_len, uint32 elapsed, bool rtcp) {
+ if (!stream_ || !data || 0 == data_len) return talk_base::SR_ERROR;
+
+ talk_base::StreamResult res = talk_base::SR_SUCCESS;
+ // Write the file header if it has not been written yet.
+ if (!file_header_written_) {
+ res = WriteFileHeader();
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+ file_header_written_ = true;
+ }
+
+ // Write the dump packet header.
+ talk_base::ByteBuffer buf;
+ buf.WriteUInt16(static_cast<uint16>(RtpDumpPacket::kHeaderLength + data_len));
+ buf.WriteUInt16(static_cast<uint16>(rtcp ? 0 : data_len));
+ buf.WriteUInt32(elapsed);
+ res = stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL);
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+
+ // Write the actual RTP or RTCP packet.
+ return stream_->WriteAll(data, data_len, NULL, NULL);
+}
+
+talk_base::StreamResult RtpDumpWriter::WriteFileHeader() {
+ talk_base::StreamResult res = stream_->WriteAll(
+ RtpDumpFileHeader::kFirstLine.c_str(),
+ RtpDumpFileHeader::kFirstLine.size(), NULL, NULL);
+ if (res != talk_base::SR_SUCCESS) {
+ return res;
+ }
+
+ talk_base::ByteBuffer buf;
+ RtpDumpFileHeader file_header(talk_base::Time(), 0, 0);
+ file_header.WriteToByteBuffer(&buf);
+ return stream_->WriteAll(buf.Data(), buf.Length(), NULL, NULL);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/rtpdump.h b/talk/session/phone/rtpdump.h
new file mode 100644
index 0000000..f87b922
--- /dev/null
+++ b/talk/session/phone/rtpdump.h
@@ -0,0 +1,203 @@
+/*
+ * libjingle
+ * Copyright 2010, 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_RTPDUMP_H_
+#define TALK_SESSION_PHONE_RTPDUMP_H_
+
+#include <cstring>
+#include <string>
+#include <vector>
+
+#include "talk/base/basictypes.h"
+#include "talk/base/stream.h"
+
+namespace talk_base {
+class ByteBuffer;
+}
+
+namespace cricket {
+
+// We use the RTP dump file format compatible to the format used by rtptools
+// (http://www.cs.columbia.edu/irt/software/rtptools/) and Wireshark
+// (http://wiki.wireshark.org/rtpdump). In particular, the file starts with the
+// first line "#!rtpplay1.0 address/port\n", followed by a 16 byte file header.
+// For each packet, the file contains a 8 byte dump packet header, followed by
+// the actual RTP or RTCP packet.
+
+struct RtpDumpFileHeader {
+ RtpDumpFileHeader(uint32 start_ms, uint32 s, uint16 p);
+ void WriteToByteBuffer(talk_base::ByteBuffer* buf);
+
+ static const std::string kFirstLine;
+ static const size_t kHeaderLength = 16;
+ uint32 start_sec; // start of recording, the seconds part.
+ uint32 start_usec; // start of recording, the microseconds part.
+ uint32 source; // network source (multicast address).
+ uint16 port; // UDP port.
+ uint16 padding; // 2 bytes padding.
+};
+
+struct RtpDumpPacket {
+ RtpDumpPacket() {}
+
+ RtpDumpPacket(const void* d, size_t s, uint32 elapsed, bool rtcp)
+ : elapsed_time(elapsed),
+ is_rtcp(rtcp) {
+ data.resize(s);
+ memcpy(&data[0], d, s);
+ }
+
+ bool IsValidRtpPacket() const;
+ // Get the sequence number, timestampe, and SSRC of the RTP packet. Return
+ // true and set the output parameter if successful.
+ bool GetRtpSeqNum(uint16* seq_num) const;
+ bool GetRtpTimestamp(uint32* ts) const;
+ bool GetRtpSsrc(uint32* ssrc) const;
+
+ static const size_t kHeaderLength = 8;
+ uint32 elapsed_time; // Milliseconds since the start of recording.
+ bool is_rtcp; // True if the data below is a RTCP packet.
+ std::vector<uint8> data; // The actual RTP or RTCP packet.
+};
+
+class RtpDumpReader {
+ public:
+ explicit RtpDumpReader(talk_base::StreamInterface* stream)
+ : stream_(stream),
+ file_header_read_(false),
+ first_line_and_file_header_len_(0),
+ start_time_ms_(0) {
+ }
+ virtual ~RtpDumpReader() {}
+
+ virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ protected:
+ talk_base::StreamResult ReadFileHeader();
+ bool RewindToFirstDumpPacket() {
+ return stream_->SetPosition(first_line_and_file_header_len_);
+ }
+
+ private:
+ // Check if its matches "#!rtpplay1.0 address/port\n".
+ bool CheckFirstLine(const std::string& first_line);
+
+ talk_base::StreamInterface* stream_;
+ bool file_header_read_;
+ size_t first_line_and_file_header_len_;
+ uint32 start_time_ms_;
+ DISALLOW_COPY_AND_ASSIGN(RtpDumpReader);
+};
+
+// RtpDumpLoopReader reads RTP dump packets from the input stream and rewinds
+// the stream when it ends. RtpDumpLoopReader maintains the elapsed time, the
+// RTP sequence number and the RTP timestamp properly. RtpDumpLoopReader can
+// handle both RTP dump and RTCP dump. We assume that the dump does not mix
+// RTP packets and RTCP packets.
+class RtpDumpLoopReader : public RtpDumpReader {
+ public:
+ explicit RtpDumpLoopReader(talk_base::StreamInterface* stream);
+ virtual talk_base::StreamResult ReadPacket(RtpDumpPacket* packet);
+
+ private:
+ // During the first loop, update the statistics, including packet count, frame
+ // count, timestamps, and sequence number, of the input stream.
+ void UpdateStreamStatistics(const RtpDumpPacket& packet);
+
+ // At the end of first loop, calculate elapsed_time_increases_,
+ // rtp_seq_num_increase_, and rtp_timestamp_increase_.
+ void CalculateIncreases();
+
+ // During the second and later loops, update the elapsed time of the dump
+ // packet. If the dumped packet is a RTP packet, update its RTP sequence
+ // number and timestamp as well.
+ void UpdateDumpPacket(RtpDumpPacket* packet);
+
+ int loop_count_;
+ // How much to increase the elapsed time, RTP sequence number, RTP timestampe
+ // for each loop. They are calcualted with the variables below during the
+ // first loop.
+ uint32 elapsed_time_increases_;
+ uint16 rtp_seq_num_increase_;
+ uint32 rtp_timestamp_increase_;
+ // How many RTP packets and how many payload frames in the input stream. RTP
+ // packets belong to the same frame have the same RTP timestamp, different
+ // dump timestamp, and different RTP sequence number.
+ uint32 packet_count_;
+ uint32 frame_count_;
+ // The elapsed time, RTP sequence number, and RTP timestamp of the first and
+ // the previous dump packets in the input stream.
+ uint32 first_elapsed_time_;
+ uint16 first_rtp_seq_num_;
+ uint32 first_rtp_timestamp_;
+ uint32 prev_elapsed_time_;
+ uint16 prev_rtp_seq_num_;
+ uint32 prev_rtp_timestamp_;
+
+ DISALLOW_COPY_AND_ASSIGN(RtpDumpLoopReader);
+};
+
+class RtpDumpWriter {
+ public:
+ explicit RtpDumpWriter(talk_base::StreamInterface* stream);
+
+ // Write a RTP or RTCP packet. The parameters data points to the packet and
+ // data_len is its length.
+ talk_base::StreamResult WriteRtpPacket(const void* data, size_t data_len) {
+ return WritePacket(data, data_len, GetElapsedTime(), false);
+ }
+ talk_base::StreamResult WriteRtcpPacket(const void* data, size_t data_len) {
+ return WritePacket(data, data_len, GetElapsedTime(), true);
+ }
+ talk_base::StreamResult WritePacket(const RtpDumpPacket& packet) {
+ return WritePacket(&packet.data[0], packet.data.size(), packet.elapsed_time,
+ packet.is_rtcp);
+ }
+ uint32 GetElapsedTime() const;
+
+ bool GetDumpSize(size_t* size) {
+ // Note that we use GetPosition(), rather than GetSize(), to avoid flush the
+ // stream per write.
+ return stream_ && size && stream_->GetPosition(size);
+ }
+
+ protected:
+ talk_base::StreamResult WriteFileHeader();
+
+ private:
+ talk_base::StreamResult WritePacket(const void* data, size_t data_len,
+ uint32 elapsed, bool rtcp);
+
+ talk_base::StreamInterface* stream_;
+ bool file_header_written_;
+ uint32 start_time_ms_; // Time when the record starts.
+ DISALLOW_COPY_AND_ASSIGN(RtpDumpWriter);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_RTPDUMP_H_
diff --git a/talk/session/phone/soundclip.cc b/talk/session/phone/soundclip.cc
new file mode 100644
index 0000000..f1069e0
--- /dev/null
+++ b/talk/session/phone/soundclip.cc
@@ -0,0 +1,82 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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/session/phone/soundclip.h"
+
+namespace cricket {
+
+enum {
+ MSG_PLAYSOUND = 1,
+};
+
+struct PlaySoundMessageData : talk_base::MessageData {
+ PlaySoundMessageData(const void *c,
+ int l,
+ SoundclipMedia::SoundclipFlags f)
+ : clip(c),
+ len(l),
+ flags(f),
+ result(false) {
+ }
+
+ const void *clip;
+ int len;
+ SoundclipMedia::SoundclipFlags flags;
+ bool result;
+};
+
+Soundclip::Soundclip(talk_base::Thread *thread, SoundclipMedia *soundclip_media)
+ : worker_thread_(thread),
+ soundclip_media_(soundclip_media) {
+}
+
+bool Soundclip::PlaySound(const void *clip,
+ int len,
+ SoundclipMedia::SoundclipFlags flags) {
+ PlaySoundMessageData data(clip, len, flags);
+ worker_thread_->Send(this, MSG_PLAYSOUND, &data);
+ return data.result;
+}
+
+bool Soundclip::PlaySound_w(const void *clip,
+ int len,
+ SoundclipMedia::SoundclipFlags flags) {
+ return soundclip_media_->PlaySound(static_cast<const char *>(clip),
+ len,
+ flags);
+}
+
+void Soundclip::OnMessage(talk_base::Message *message) {
+ ASSERT(message->message_id == MSG_PLAYSOUND);
+ PlaySoundMessageData *data =
+ static_cast<PlaySoundMessageData *>(message->pdata);
+ data->result = PlaySound_w(data->clip,
+ data->len,
+ data->flags);
+}
+
+} // namespace cricket
diff --git a/talk/session/phone/soundclip.h b/talk/session/phone/soundclip.h
new file mode 100644
index 0000000..4038477
--- /dev/null
+++ b/talk/session/phone/soundclip.h
@@ -0,0 +1,70 @@
+/*
+ * libjingle
+ * Copyright 2004--2009, 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_SOUNDCLIP_H_
+#define TALK_SESSION_PHONE_SOUNDCLIP_H_
+
+#include "talk/base/scoped_ptr.h"
+#include "talk/session/phone/mediaengine.h"
+
+namespace talk_base {
+
+class Thread;
+
+}
+
+namespace cricket {
+
+// Soundclip wraps SoundclipMedia to support marshalling calls to the proper
+// thread.
+class Soundclip : private talk_base::MessageHandler {
+ public:
+ Soundclip(talk_base::Thread* thread, SoundclipMedia* soundclip_media);
+
+ // Plays a sound out to the speakers with the given audio stream. The stream
+ // must be 16-bit little-endian 16 kHz PCM. If a stream is already playing
+ // on this Soundclip, it is stopped. If clip is NULL, nothing is played.
+ // Returns whether it was successful.
+ bool PlaySound(const void* clip,
+ int len,
+ SoundclipMedia::SoundclipFlags flags);
+
+ private:
+ bool PlaySound_w(const void* clip,
+ int len,
+ SoundclipMedia::SoundclipFlags flags);
+
+ // From MessageHandler
+ virtual void OnMessage(talk_base::Message* message);
+
+ talk_base::Thread* worker_thread_;
+ talk_base::scoped_ptr<SoundclipMedia> soundclip_media_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_SOUNDCLIP_H_
diff --git a/talk/session/phone/srtpfilter.cc b/talk/session/phone/srtpfilter.cc
new file mode 100644
index 0000000..71f1991
--- /dev/null
+++ b/talk/session/phone/srtpfilter.cc
@@ -0,0 +1,493 @@
+/*
+ * libjingle
+ * Copyright 2009, 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.
+ */
+
+// talk's config.h, generated from mac_config_dot_h for OSX, conflicts with the
+// one included by the libsrtp headers. Don't use it. Instead, we keep HAVE_SRTP
+// and LOGGING defined in config.h.
+#undef HAVE_CONFIG_H
+
+#ifdef OSX
+// TODO: For the XCode build, we force SRTP (b/2500074)
+#ifndef HAVE_SRTP
+#define HAVE_SRTP 1
+#endif // HAVE_SRTP
+// If LOGGING is not defined, define it to 1 (b/3245816)
+#ifndef LOGGING
+#define LOGGING 1
+#endif // HAVE_SRTP
+#endif
+
+#include "talk/session/phone/srtpfilter.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "talk/base/base64.h"
+#include "talk/base/logging.h"
+
+// Enable this line to turn on SRTP debugging
+// #define SRTP_DEBUG
+
+#ifdef HAVE_SRTP
+#ifdef SRTP_RELATIVE_PATH
+#include "srtp.h" // NOLINT
+#else
+#include "third_party/libsrtp/include/srtp.h"
+#endif // SRTP_RELATIVE_PATH
+#ifdef _DEBUG
+extern "C" debug_module_t mod_srtp;
+#endif
+#else
+// SrtpFilter needs that constant.
+#define SRTP_MASTER_KEY_LEN 30
+#endif // HAVE_SRTP
+
+namespace cricket {
+
+const std::string& CS_DEFAULT = CS_AES_CM_128_HMAC_SHA1_80;
+const std::string CS_AES_CM_128_HMAC_SHA1_80 = "AES_CM_128_HMAC_SHA1_80";
+const std::string CS_AES_CM_128_HMAC_SHA1_32 = "AES_CM_128_HMAC_SHA1_32";
+const int SRTP_MASTER_KEY_BASE64_LEN = SRTP_MASTER_KEY_LEN * 4 / 3;
+
+SrtpFilter::SrtpFilter() : state_(ST_INIT) {
+}
+
+SrtpFilter::~SrtpFilter() {
+}
+
+bool SrtpFilter::IsActive() const {
+ return (state_ == ST_ACTIVE);
+}
+
+bool SrtpFilter::SetOffer(const std::vector<CryptoParams>& offer_params,
+ ContentSource source) {
+ bool ret = false;
+ if (state_ == ST_INIT) {
+ ret = StoreParams(offer_params, source);
+ } else {
+ LOG(LS_ERROR) << "Invalid state for SRTP offer";
+ }
+ return ret;
+}
+
+bool SrtpFilter::SetAnswer(const std::vector<CryptoParams>& answer_params,
+ ContentSource source) {
+ bool ret = false;
+ if ((state_ == ST_SENTOFFER && source == CS_REMOTE) ||
+ (state_ == ST_RECEIVEDOFFER && source == CS_LOCAL)) {
+ // If the answer requests crypto, finalize the parameters and apply them.
+ // Otherwise, complete the negotiation of a unencrypted session.
+ if (!answer_params.empty()) {
+ CryptoParams selected_params;
+ ret = NegotiateParams(answer_params, &selected_params);
+ if (ret) {
+ if (state_ == ST_SENTOFFER) {
+ ret = ApplyParams(selected_params, answer_params[0]);
+ } else { // ST_RECEIVEDOFFER
+ ret = ApplyParams(answer_params[0], selected_params);
+ }
+ }
+ } else {
+ ret = ResetParams();
+ }
+ } else {
+ LOG(LS_ERROR) << "Invalid state for SRTP answer";
+ }
+ return ret;
+}
+
+bool SrtpFilter::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
+ if (!IsActive()) {
+ LOG(LS_WARNING) << "Failed to ProtectRtp: SRTP not active";
+ return false;
+ }
+ return send_session_.ProtectRtp(p, in_len, max_len, out_len);
+}
+
+bool SrtpFilter::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+ if (!IsActive()) {
+ LOG(LS_WARNING) << "Failed to ProtectRtcp: SRTP not active";
+ return false;
+ }
+ return send_session_.ProtectRtcp(p, in_len, max_len, out_len);
+}
+
+bool SrtpFilter::UnprotectRtp(void* p, int in_len, int* out_len) {
+ if (!IsActive()) {
+ LOG(LS_WARNING) << "Failed to UnprotectRtp: SRTP not active";
+ return false;
+ }
+ return recv_session_.UnprotectRtp(p, in_len, out_len);
+}
+
+bool SrtpFilter::UnprotectRtcp(void* p, int in_len, int* out_len) {
+ if (!IsActive()) {
+ LOG(LS_WARNING) << "Failed to UnprotectRtcp: SRTP not active";
+ return false;
+ }
+ return recv_session_.UnprotectRtcp(p, in_len, out_len);
+}
+
+
+bool SrtpFilter::StoreParams(const std::vector<CryptoParams>& params,
+ ContentSource source) {
+ offer_params_ = params;
+ state_ = (source == CS_LOCAL) ? ST_SENTOFFER : ST_RECEIVEDOFFER;
+ return true;
+}
+
+bool SrtpFilter::NegotiateParams(const std::vector<CryptoParams>& answer_params,
+ CryptoParams* selected_params) {
+ // We're processing an accept. We should have exactly one set of params,
+ // unless the offer didn't mention crypto, in which case we shouldn't be here.
+ bool ret = (answer_params.size() == 1U && !offer_params_.empty());
+ if (ret) {
+ // We should find a match between the answer params and the offered params.
+ std::vector<CryptoParams>::const_iterator it;
+ for (it = offer_params_.begin(); it != offer_params_.end(); ++it) {
+ if (answer_params[0].Matches(*it)) {
+ break;
+ }
+ }
+
+ if (it != offer_params_.end()) {
+ *selected_params = *it;
+ } else {
+ ret = false;
+ }
+ }
+
+ if (!ret) {
+ LOG(LS_WARNING) << "Invalid parameters in SRTP answer";
+ }
+ return ret;
+}
+
+bool SrtpFilter::ApplyParams(const CryptoParams& send_params,
+ const CryptoParams& recv_params) {
+ // TODO: Zero these buffers after use.
+ bool ret;
+ uint8 send_key[SRTP_MASTER_KEY_LEN], recv_key[SRTP_MASTER_KEY_LEN];
+ ret = (ParseKeyParams(send_params.key_params, send_key, sizeof(send_key)) &&
+ ParseKeyParams(recv_params.key_params, recv_key, sizeof(recv_key)));
+ if (ret) {
+ ret = (send_session_.SetSend(send_params.cipher_suite,
+ send_key, sizeof(send_key)) &&
+ recv_session_.SetRecv(recv_params.cipher_suite,
+ recv_key, sizeof(recv_key)));
+ }
+ if (ret) {
+ offer_params_.clear();
+ state_ = ST_ACTIVE;
+ LOG(LS_INFO) << "SRTP activated with negotiated parameters:"
+ << " send cipher_suite " << send_params.cipher_suite
+ << " recv cipher_suite " << recv_params.cipher_suite;
+ } else {
+ LOG(LS_WARNING) << "Failed to apply negotiated SRTP parameters";
+ }
+ return ret;
+}
+
+bool SrtpFilter::ResetParams() {
+ offer_params_.clear();
+ state_ = ST_INIT;
+ LOG(LS_INFO) << "SRTP reset to init state";
+ return true;
+}
+
+bool SrtpFilter::ParseKeyParams(const std::string& key_params,
+ uint8* key, int len) {
+ // example key_params: "inline:YUJDZGVmZ2hpSktMbW9QUXJzVHVWd3l6MTIzNDU2"
+
+ // Fail if key-method is wrong.
+ if (key_params.find("inline:") != 0) {
+ return false;
+ }
+
+ // Fail if base64 decode fails, or the key is the wrong size.
+ std::string key_b64(key_params.substr(7)), key_str;
+ if (!talk_base::Base64::Decode(key_b64, talk_base::Base64::DO_STRICT,
+ &key_str, NULL) ||
+ static_cast<int>(key_str.size()) != len) {
+ return false;
+ }
+
+ memcpy(key, key_str.c_str(), len);
+ return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// SrtpSession
+
+#ifdef HAVE_SRTP
+
+bool SrtpSession::inited_ = false;
+std::list<SrtpSession*> SrtpSession::sessions_;
+
+SrtpSession::SrtpSession()
+ : session_(NULL), rtp_auth_tag_len_(0), rtcp_auth_tag_len_(0) {
+ sessions_.push_back(this);
+}
+
+SrtpSession::~SrtpSession() {
+ sessions_.erase(std::find(sessions_.begin(), sessions_.end(), this));
+ if (session_) {
+ srtp_dealloc(session_);
+ }
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+ return SetKey(ssrc_any_outbound, cs, key, len);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+ return SetKey(ssrc_any_inbound, cs, key, len);
+}
+
+bool SrtpSession::ProtectRtp(void* p, int in_len, int max_len, int* out_len) {
+ if (!session_) {
+ LOG(LS_WARNING) << "Failed to protect SRTP packet: no SRTP Session";
+ return false;
+ }
+
+ int need_len = in_len + rtp_auth_tag_len_; // NOLINT
+ if (max_len < need_len) {
+ LOG(LS_WARNING) << "Failed to protect SRTP packet: The buffer length "
+ << max_len << " is less than the needed " << need_len;
+ return false;
+ }
+
+ *out_len = in_len;
+ int err = srtp_protect(session_, p, out_len);
+ if (err != err_status_ok) {
+ LOG(LS_WARNING) << "Failed to protect SRTP packet, err=" << err;
+ return false;
+ }
+ return true;
+}
+
+bool SrtpSession::ProtectRtcp(void* p, int in_len, int max_len, int* out_len) {
+ if (!session_) {
+ LOG(LS_WARNING) << "Failed to protect SRTCP packet: no SRTP Session";
+ return false;
+ }
+
+ int need_len = in_len + sizeof(uint32) + rtcp_auth_tag_len_; // NOLINT
+ if (max_len < need_len) {
+ LOG(LS_WARNING) << "Failed to protect SRTCP packet: The buffer length "
+ << max_len << " is less than the needed " << need_len;
+ return false;
+ }
+
+ *out_len = in_len;
+ int err = srtp_protect_rtcp(session_, p, out_len);
+ if (err != err_status_ok) {
+ LOG(LS_WARNING) << "Failed to protect SRTCP packet, err=" << err;
+ return false;
+ }
+ return true;
+}
+
+bool SrtpSession::UnprotectRtp(void* p, int in_len, int* out_len) {
+ if (!session_) {
+ LOG(LS_WARNING) << "Failed to unprotect SRTP packet: no SRTP Session";
+ return false;
+ }
+
+ *out_len = in_len;
+ int err = srtp_unprotect(session_, p, out_len);
+ if (err != err_status_ok) {
+ LOG(LS_WARNING) << "Failed to unprotect SRTP packet, err=" << err;
+ return false;
+ }
+ return true;
+}
+
+bool SrtpSession::UnprotectRtcp(void* p, int in_len, int* out_len) {
+ if (!session_) {
+ LOG(LS_WARNING) << "Failed to unprotect SRTCP packet: no SRTP Session";
+ return false;
+ }
+
+ *out_len = in_len;
+ int err = srtp_unprotect_rtcp(session_, p, out_len);
+ if (err != err_status_ok) {
+ LOG(LS_WARNING) << "Failed to unprotect SRTCP packet, err=" << err;
+ return false;
+ }
+ return true;
+}
+
+bool SrtpSession::SetKey(int type, const std::string& cs,
+ const uint8* key, int len) {
+ if (session_) {
+ LOG(LS_ERROR) << "Failed to create SRTP session: "
+ << "SRTP session already created";
+ return false;
+ }
+
+ if (!Init()) {
+ return false;
+ }
+
+ srtp_policy_t policy;
+ memset(&policy, 0, sizeof(policy));
+
+ if (cs == CS_AES_CM_128_HMAC_SHA1_80) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtp);
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp);
+ } else if (cs == CS_AES_CM_128_HMAC_SHA1_32) {
+ crypto_policy_set_aes_cm_128_hmac_sha1_32(&policy.rtp); // rtp is 32,
+ crypto_policy_set_aes_cm_128_hmac_sha1_80(&policy.rtcp); // rtcp still 80
+ } else {
+ LOG(LS_WARNING) << "Failed to create SRTP session: unsupported"
+ << " cipher_suite " << cs.c_str();
+ return false;
+ }
+
+ if (!key || len != SRTP_MASTER_KEY_LEN) {
+ LOG(LS_WARNING) << "Failed to create SRTP session: invalid key";
+ return false;
+ }
+
+ policy.ssrc.type = static_cast<ssrc_type_t>(type);
+ policy.ssrc.value = 0;
+ policy.key = const_cast<uint8*>(key);
+ // TODO parse window size from WSH session-param
+ policy.window_size = 1024;
+ policy.allow_repeat_tx = 1;
+ policy.next = NULL;
+
+ int err = srtp_create(&session_, &policy);
+ if (err != err_status_ok) {
+ LOG(LS_ERROR) << "Failed to create SRTP session, err=" << err;
+ return false;
+ }
+
+ rtp_auth_tag_len_ = policy.rtp.auth_tag_len;
+ rtcp_auth_tag_len_ = policy.rtcp.auth_tag_len;
+ return true;
+}
+
+bool SrtpSession::Init() {
+ if (!inited_) {
+ int err;
+#ifdef DEBUG_SRTP
+ debug_on(mod_srtp);
+#endif
+ err = srtp_init();
+ if (err != err_status_ok) {
+ LOG(LS_ERROR) << "Failed to init SRTP, err=" << err;
+ return false;
+ }
+
+ err = srtp_install_event_handler(&SrtpSession::HandleEventThunk);
+ if (err != err_status_ok) {
+ LOG(LS_ERROR) << "Failed to install SRTP event handler, err=" << err;
+ return false;
+ }
+
+ inited_ = true;
+ }
+
+ return true;
+}
+
+void SrtpSession::HandleEvent(const srtp_event_data_t* ev) {
+ switch (ev->event) {
+ case event_ssrc_collision:
+ LOG(LS_INFO) << "SRTP event: SSRC collision";
+ break;
+ case event_key_soft_limit:
+ LOG(LS_INFO) << "SRTP event: reached soft key usage limit";
+ break;
+ case event_key_hard_limit:
+ LOG(LS_INFO) << "SRTP event: reached hard key usage limit";
+ break;
+ case event_packet_index_limit:
+ LOG(LS_INFO) << "SRTP event: reached hard packet limit (2^48 packets)";
+ break;
+ default:
+ LOG(LS_INFO) << "SRTP event: unknown " << ev->event;
+ break;
+ }
+}
+
+void SrtpSession::HandleEventThunk(srtp_event_data_t* ev) {
+ for (std::list<SrtpSession*>::iterator it = sessions_.begin();
+ it != sessions_.end(); ++it) {
+ if ((*it)->session_ == ev->session) {
+ (*it)->HandleEvent(ev);
+ break;
+ }
+ }
+}
+
+#else // !HAVE_SRTP
+
+namespace {
+bool SrtpNotAvailable(const char *func) {
+ LOG(LS_ERROR) << func << ": SRTP is not available on your system.";
+ return false;
+}
+} // anonymous namespace
+
+SrtpSession::SrtpSession() {
+ LOG(WARNING) << "SRTP implementation is missing.";
+}
+
+SrtpSession::~SrtpSession() {
+}
+
+bool SrtpSession::SetSend(const std::string& cs, const uint8* key, int len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::SetRecv(const std::string& cs, const uint8* key, int len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtp(void* data, int in_len, int max_len,
+ int* out_len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::ProtectRtcp(void* data, int in_len, int max_len,
+ int* out_len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtp(void* data, int in_len, int* out_len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+bool SrtpSession::UnprotectRtcp(void* data, int in_len, int* out_len) {
+ return SrtpNotAvailable(__FUNCTION__);
+}
+
+#endif // HAVE_SRTP
+} // namespace cricket
diff --git a/talk/session/phone/srtpfilter.h b/talk/session/phone/srtpfilter.h
new file mode 100644
index 0000000..3401004
--- /dev/null
+++ b/talk/session/phone/srtpfilter.h
@@ -0,0 +1,147 @@
+/*
+ * libjingle
+ * Copyright 2009, 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_SRTPFILTER_H_
+#define TALK_SESSION_PHONE_SRTPFILTER_H_
+
+#include <list>
+#include <string>
+#include <vector>
+#include "talk/base/basictypes.h"
+#include "talk/session/phone/cryptoparams.h"
+#include "talk/p2p/base/sessiondescription.h"
+
+// Forward declaration to avoid pulling in libsrtp headers here
+struct srtp_event_data_t;
+struct srtp_ctx_t;
+typedef srtp_ctx_t* srtp_t;
+struct srtp_policy_t;
+
+namespace cricket {
+
+// Cipher suite to use for SRTP. Typically a 80-bit HMAC will be used, except
+// in applications (voice) where the additional bandwidth may be significant.
+// A 80-bit HMAC is always used for SRTCP.
+extern const std::string& CS_DEFAULT;
+// 128-bit AES with 80-bit SHA-1 HMAC.
+extern const std::string CS_AES_CM_128_HMAC_SHA1_80;
+// 128-bit AES with 32-bit SHA-1 HMAC.
+extern const std::string CS_AES_CM_128_HMAC_SHA1_32;
+// Key is 128 bits and salt is 112 bits == 30 bytes. B64 bloat => 40 bytes.
+extern const int SRTP_MASTER_KEY_BASE64_LEN;
+
+// Class that wraps a libSRTP session. Used internally by SrtpFilter, below.
+class SrtpSession {
+ public:
+ SrtpSession();
+ ~SrtpSession();
+
+ // Configures the session for sending data using the specified
+ // cipher-suite and key. Receiving must be done by a separate session.
+ bool SetSend(const std::string& cs, const uint8* key, int len);
+ // Configures the session for receiving data using the specified
+ // cipher-suite and key. Sending must be done by a separate session.
+ bool SetRecv(const std::string& cs, const uint8* key, int len);
+
+ // Encrypts/signs an individual RTP/RTCP packet, in-place.
+ // If an HMAC is used, this will increase the packet size.
+ bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
+ bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+ // Decrypts/verifies an invidiual RTP/RTCP packet.
+ // If an HMAC is used, this will decrease the packet size.
+ bool UnprotectRtp(void* data, int in_len, int* out_len);
+ bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+ private:
+ bool SetKey(int type, const std::string& cs, const uint8* key, int len);
+ static bool Init();
+ void HandleEvent(const srtp_event_data_t* ev);
+ static void HandleEventThunk(srtp_event_data_t* ev);
+
+ srtp_t session_;
+ int rtp_auth_tag_len_;
+ int rtcp_auth_tag_len_;
+ static bool inited_;
+ static std::list<SrtpSession*> sessions_;
+};
+
+// Class to transform SRTP to/from RTP.
+// Initialize by calling SetSend with the local security params, then call
+// SetRecv once the remote security params are received. At that point
+// Protect/UnprotectRt(c)p can be called to encrypt/decrypt data.
+// TODO: Figure out concurrency policy for SrtpFilter.
+class SrtpFilter {
+ public:
+ SrtpFilter();
+ ~SrtpFilter();
+
+ // Whether the filter is active (i.e. crypto has been properly negotiated).
+ bool IsActive() const;
+
+ // Indicates which crypto algorithms and keys were contained in the offer.
+ // offer_params should contain a list of available parameters to use, or none,
+ // if crypto is not desired. This must be called before SetAnswer.
+ bool SetOffer(const std::vector<CryptoParams>& offer_params,
+ ContentSource source);
+ // Indicates which crypto algorithms and keys were contained in the answer.
+ // answer_params should contain the negotiated parameters, which may be none,
+ // if crypto was not desired or could not be negotiated (and not required).
+ // This must be called after SetOffer. If crypto negotiation completes
+ // successfully, this will advance the filter to the active state.
+ bool SetAnswer(const std::vector<CryptoParams>& answer_params,
+ ContentSource source);
+
+ // Encrypts/signs an individual RTP/RTCP packet, in-place.
+ // If an HMAC is used, this will increase the packet size.
+ bool ProtectRtp(void* data, int in_len, int max_len, int* out_len);
+ bool ProtectRtcp(void* data, int in_len, int max_len, int* out_len);
+ // Decrypts/verifies an invidiual RTP/RTCP packet.
+ // If an HMAC is used, this will decrease the packet size.
+ bool UnprotectRtp(void* data, int in_len, int* out_len);
+ bool UnprotectRtcp(void* data, int in_len, int* out_len);
+
+ protected:
+ bool StoreParams(const std::vector<CryptoParams>& offer_params,
+ ContentSource source);
+ bool NegotiateParams(const std::vector<CryptoParams>& answer_params,
+ CryptoParams* selected_params);
+ bool ApplyParams(const CryptoParams& send_params,
+ const CryptoParams& recv_params);
+ bool ResetParams();
+ static bool ParseKeyParams(const std::string& params, uint8* key, int len);
+
+ private:
+ enum State { ST_INIT, ST_SENTOFFER, ST_RECEIVEDOFFER, ST_ACTIVE };
+ State state_;
+ std::vector<CryptoParams> offer_params_;
+ SrtpSession send_session_;
+ SrtpSession recv_session_;
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_SRTPFILTER_H_
diff --git a/talk/session/phone/testdata/video.rtpdump b/talk/session/phone/testdata/video.rtpdump
new file mode 100644
index 0000000..7be863e
--- /dev/null
+++ b/talk/session/phone/testdata/video.rtpdump
Binary files differ
diff --git a/talk/session/phone/testdata/voice.rtpdump b/talk/session/phone/testdata/voice.rtpdump
new file mode 100644
index 0000000..8f0ec15
--- /dev/null
+++ b/talk/session/phone/testdata/voice.rtpdump
Binary files differ
diff --git a/talk/session/phone/v4llookup.cc b/talk/session/phone/v4llookup.cc
new file mode 100644
index 0000000..e8436e1
--- /dev/null
+++ b/talk/session/phone/v4llookup.cc
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2009, Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#include "talk/session/phone/v4llookup.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/types.h>
+#include <linux/videodev2.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <cstring>
+
+#include "talk/base/logging.h"
+
+namespace cricket {
+
+V4LLookup *V4LLookup::v4l_lookup_ = new V4LLookup();
+
+bool V4LLookup::CheckIsV4L2Device(const std::string& device_path) {
+ // check device major/minor numbers are in the range for video devices.
+ struct stat s;
+
+ if (lstat(device_path.c_str(), &s) != 0 || !S_ISCHR(s.st_mode)) return false;
+
+ int video_fd = -1;
+ bool is_v4l2 = false;
+
+ // check major/minur device numbers are in range for video device
+ if (major(s.st_rdev) == 81) {
+ dev_t num = minor(s.st_rdev);
+ if (num <= 63 && num >= 0) {
+ video_fd = ::open(device_path.c_str(), O_RDONLY | O_NONBLOCK);
+ if ((video_fd >= 0) || (errno == EBUSY)) {
+ ::v4l2_capability video_caps;
+ memset(&video_caps, 0, sizeof(video_caps));
+
+ if ((errno == EBUSY) ||
+ (::ioctl(video_fd, VIDIOC_QUERYCAP, &video_caps) >= 0 &&
+ (video_caps.capabilities & V4L2_CAP_VIDEO_CAPTURE))) {
+ LOG(LS_INFO) << "Found V4L2 capture device " << device_path;
+
+ is_v4l2 = true;
+ } else {
+ LOG(LS_ERROR) << "VIDIOC_QUERYCAP failed for " << device_path;
+ }
+ } else {
+ LOG(LS_ERROR) << "Failed to open " << device_path;
+ }
+ }
+ }
+
+ if (video_fd >= 0)
+ ::close(video_fd);
+
+ return is_v4l2;
+}
+
+}; // namespace cricket
diff --git a/talk/session/phone/v4llookup.h b/talk/session/phone/v4llookup.h
new file mode 100644
index 0000000..1150172
--- /dev/null
+++ b/talk/session/phone/v4llookup.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2009, Google Inc.
+ * Author: lexnikitin@google.com (Alexey Nikitin)
+ *
+ * V4LLookup provides basic functionality to work with V2L2 devices in Linux
+ * The functionality is implemented as a class with virtual methods for
+ * the purpose of unit testing.
+ */
+#ifndef TALK_SESSION_PHONE_V4LLOOKUP_H_
+#define TALK_SESSION_PHONE_V4LLOOKUP_H_
+
+#include <string>
+
+#ifdef LINUX
+namespace cricket {
+class V4LLookup {
+ public:
+ virtual ~V4LLookup() {}
+
+ 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() {
+ return v4l_lookup_;
+ }
+
+ 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 // TALK_SESSION_PHONE_V4LLOOKUP_H_
diff --git a/talk/session/phone/videocommon.h b/talk/session/phone/videocommon.h
new file mode 100644
index 0000000..4fb311a
--- /dev/null
+++ b/talk/session/phone/videocommon.h
@@ -0,0 +1,171 @@
+// 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.
+//
+// Common definition for video, including fourcc and VideoFormat
+
+#ifndef TALK_SESSION_PHONE_VIDEOCOMMON_H_
+#define TALK_SESSION_PHONE_VIDEOCOMMON_H_
+
+#include <string>
+
+#include "talk/base/basictypes.h"
+
+namespace cricket {
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of fourcc.
+//////////////////////////////////////////////////////////////////////////////
+// Convert four characters to a fourcc code.
+// Needs to be a macro otherwise the OS X compiler complains when the kFormat*
+// constants are used in a switch.
+#define FOURCC(a, b, c, d) (\
+ (static_cast<uint32>(a)) | (static_cast<uint32>(b) << 8) | \
+ (static_cast<uint32>(c) << 16) | (static_cast<uint32>(d) << 24))
+
+// Get the name, that is, string with four characters, of a fourcc code.
+inline std::string GetFourccName(uint32 fourcc) {
+ std::string name;
+ name.push_back(static_cast<char>(fourcc & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 8) & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 16) & 0xFF));
+ name.push_back(static_cast<char>((fourcc >> 24) & 0xFF));
+ return name;
+}
+
+// FourCC codes used in Google Talk.
+// Some good pages discussing FourCC codes:
+// http://developer.apple.com/quicktime/icefloe/dispatch020.html
+// http://www.fourcc.org/yuv.php
+enum FourCC {
+ // Canonical fourcc codes used in our code.
+ FOURCC_I420 = FOURCC('I', '4', '2', '0'),
+ FOURCC_YUY2 = FOURCC('Y', 'U', 'Y', '2'),
+ FOURCC_UYVY = FOURCC('U', 'Y', 'V', 'Y'),
+ FOURCC_M420 = FOURCC('M', '4', '2', '0'),
+ FOURCC_24BG = FOURCC('2', '4', 'B', 'G'),
+ FOURCC_ABGR = FOURCC('A', 'B', 'G', 'R'),
+ FOURCC_BGRA = FOURCC('B', 'G', 'R', 'A'),
+ FOURCC_ARGB = FOURCC('A', 'R', 'G', 'B'),
+ FOURCC_MJPG = FOURCC('M', 'J', 'P', 'G'),
+ FOURCC_RAW = FOURCC('r', 'a', 'w', ' '),
+ FOURCC_NV21 = FOURCC('N', 'V', '2', '1'),
+ FOURCC_NV12 = FOURCC('N', 'V', '1', '2'),
+ // Next four are Bayer RGB formats. The four characters define the order of
+ // the colours in each 2x2 pixel grid, going left-to-right and top-to-bottom.
+ FOURCC_RGGB = FOURCC('R', 'G', 'G', 'B'),
+ FOURCC_BGGR = FOURCC('B', 'G', 'G', 'R'),
+ FOURCC_GRBG = FOURCC('G', 'R', 'B', 'G'),
+ FOURCC_GBRG = FOURCC('G', 'B', 'R', 'G'),
+
+ // Aliases for canonical fourcc codes, replaced with their canonical
+ // equivalents by CanonicalFourCC().
+ FOURCC_IYUV = FOURCC('I', 'Y', 'U', 'V'), // Alias for I420
+ FOURCC_YU12 = FOURCC('Y', 'U', '1', '2'), // Alias for I420
+ FOURCC_YUYV = FOURCC('Y', 'U', 'Y', 'V'), // Alias for YUY2
+ FOURCC_YUVS = FOURCC('y', 'u', 'v', 's'), // Alias for YUY2 on Mac
+ FOURCC_HDYC = FOURCC('H', 'D', 'Y', 'C'), // Alias for UYVY
+ FOURCC_2VUY = FOURCC('2', 'v', 'u', 'y'), // Alias for UYVY
+ FOURCC_JPEG = FOURCC('J', 'P', 'E', 'G'), // Alias for MJPG
+ FOURCC_BA81 = FOURCC('B', 'A', '8', '1'), // Alias for BGGR
+
+ // Match any fourcc.
+ FOURCC_ANY = 0xFFFFFFFF,
+};
+
+// Converts fourcc aliases into canonical ones.
+uint32 CanonicalFourCC(uint32 fourcc);
+
+//////////////////////////////////////////////////////////////////////////////
+// Definition of VideoFormat.
+//////////////////////////////////////////////////////////////////////////////
+
+static const int64 kNumNanosecsPerSec = 1000000000;
+
+struct VideoFormat {
+ static const int64 kMinimumInterval = kNumNanosecsPerSec / 10000; // 10k fps
+
+ VideoFormat() : width(0), height(0), interval(0), fourcc(0) {}
+
+ VideoFormat(int w, int h, int64 interval_ns, uint32 cc)
+ : width(w),
+ height(h),
+ interval(interval_ns),
+ fourcc(cc) {
+ }
+
+ VideoFormat(const VideoFormat& format)
+ : width(format.width),
+ height(format.height),
+ interval(format.interval),
+ fourcc(format.fourcc) {
+ }
+
+ static int64 FpsToInterval(int fps) {
+ return fps ? kNumNanosecsPerSec / fps : kMinimumInterval;
+ }
+
+ static int IntervalToFps(int64 interval) {
+ // Normalize the interval first.
+ interval = talk_base::_max(interval, kMinimumInterval);
+ return static_cast<int>(kNumNanosecsPerSec / interval);
+ }
+
+ bool operator==(const VideoFormat& format) const {
+ return width == format.width && height == format.height &&
+ interval == format.interval && fourcc == format.fourcc;
+ }
+
+ bool operator!=(const VideoFormat& format) const {
+ return !(*this == format);
+ }
+
+ bool operator<(const VideoFormat& format) const {
+ return (fourcc < format.fourcc) ||
+ (fourcc == format.fourcc && width < format.width) ||
+ (fourcc == format.fourcc && width == format.width &&
+ height < format.height) ||
+ (fourcc == format.fourcc && width == format.width &&
+ height == format.height && interval > format.interval);
+ }
+
+ int framerate() const { return IntervalToFps(interval); }
+
+ int width; // in number of pixels
+ int height; // in number of pixels
+ int64 interval; // in nanoseconds
+ uint32 fourcc; // color space. FOURCC_ANY means that any color space is OK.
+};
+
+// Result of video capturer start.
+enum CaptureResult {
+ CR_SUCCESS, // The capturer starts successfully.
+ CR_PENDING, // The capturer is pending to start the capture device.
+ CR_FAILURE, // The capturer fails to start.
+ CR_NO_DEVICE, // The capturer has no device and fails to start.
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_PHONE_VIDEOCOMMON_H_
diff --git a/talk/session/phone/voicechannel.h b/talk/session/phone/voicechannel.h
new file mode 100644
index 0000000..95de637
--- /dev/null
+++ b/talk/session/phone/voicechannel.h
@@ -0,0 +1,33 @@
+/*
+ * libjingle
+ * Copyright 2004--2007, 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 _VOICECHANNEL_H_
+#define _VOICECHANNEL_H_
+
+#include "talk/session/phone/channel.h"
+
+#endif // _VOICECHANNEL_H_
diff --git a/talk/session/tunnel/pseudotcpchannel.cc b/talk/session/tunnel/pseudotcpchannel.cc
new file mode 100644
index 0000000..0448853
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.cc
@@ -0,0 +1,571 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "pseudotcpchannel.h"
+
+using namespace talk_base;
+
+namespace cricket {
+
+extern const talk_base::ConstantLabel SESSION_STATES[];
+
+// MSG_WK_* - worker thread messages
+// MSG_ST_* - stream thread messages
+// MSG_SI_* - signal thread messages
+
+enum {
+ MSG_WK_CLOCK = 1,
+ MSG_WK_PURGE,
+ MSG_ST_EVENT,
+ MSG_SI_DESTROYCHANNEL,
+ MSG_SI_DESTROY,
+};
+
+struct EventData : public MessageData {
+ int event, error;
+ EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel::InternalStream : public StreamInterface {
+public:
+ InternalStream(PseudoTcpChannel* parent);
+ virtual ~InternalStream();
+
+ virtual StreamState GetState() const;
+ virtual StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ virtual StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ virtual void Close();
+
+private:
+ // parent_ is accessed and modified exclusively on the event thread, to
+ // avoid thread contention. This means that the PseudoTcpChannel cannot go
+ // away until after it receives a Close() from TunnelStream.
+ PseudoTcpChannel* parent_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel
+// Member object lifetime summaries:
+// session_ - passed in constructor, cleared when channel_ goes away.
+// channel_ - created in Connect, destroyed when session_ or tcp_ goes away.
+// tcp_ - created in Connect, destroyed when channel_ goes away, or connection
+// closes.
+// worker_thread_ - created when channel_ is created, purged when channel_ is
+// destroyed.
+// stream_ - created in GetStream, destroyed by owner at arbitrary time.
+// this - created in constructor, destroyed when worker_thread_ and stream_
+// are both gone.
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signal thread methods
+//
+
+PseudoTcpChannel::PseudoTcpChannel(Thread* stream_thread, Session* session)
+ : signal_thread_(session->session_manager()->signaling_thread()),
+ worker_thread_(NULL),
+ stream_thread_(stream_thread),
+ session_(session), channel_(NULL), tcp_(NULL), stream_(NULL),
+ stream_readable_(false), pending_read_event_(false),
+ ready_to_connect_(false) {
+ ASSERT(signal_thread_->IsCurrent());
+ ASSERT(NULL != session_);
+}
+
+PseudoTcpChannel::~PseudoTcpChannel() {
+ ASSERT(signal_thread_->IsCurrent());
+ ASSERT(worker_thread_ == NULL);
+ ASSERT(session_ == NULL);
+ ASSERT(channel_ == NULL);
+ ASSERT(stream_ == NULL);
+ ASSERT(tcp_ == NULL);
+}
+
+bool PseudoTcpChannel::Connect(const std::string& content_name,
+ const std::string& channel_name) {
+ ASSERT(signal_thread_->IsCurrent());
+ CritScope lock(&cs_);
+
+ if (channel_)
+ return false;
+
+ ASSERT(session_ != NULL);
+ worker_thread_ = session_->session_manager()->worker_thread();
+ content_name_ = content_name;
+ channel_ = session_->CreateChannel(content_name, channel_name);
+ channel_name_ = channel_name;
+ channel_->SetOption(Socket::OPT_DONTFRAGMENT, 1);
+
+ channel_->SignalDestroyed.connect(this,
+ &PseudoTcpChannel::OnChannelDestroyed);
+ channel_->SignalWritableState.connect(this,
+ &PseudoTcpChannel::OnChannelWritableState);
+ channel_->SignalReadPacket.connect(this,
+ &PseudoTcpChannel::OnChannelRead);
+ channel_->SignalRouteChange.connect(this,
+ &PseudoTcpChannel::OnChannelConnectionChanged);
+
+ ASSERT(tcp_ == NULL);
+ tcp_ = new PseudoTcp(this, 0);
+ if (session_->initiator()) {
+ // Since we may try several protocols and network adapters that won't work,
+ // waiting until we get our first writable notification before initiating
+ // TCP negotiation.
+ ready_to_connect_ = true;
+ }
+
+ return true;
+}
+
+StreamInterface* PseudoTcpChannel::GetStream() {
+ ASSERT(signal_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ ASSERT(NULL != session_);
+ if (!stream_)
+ stream_ = new PseudoTcpChannel::InternalStream(this);
+ //TODO("should we disallow creation of new stream at some point?");
+ return stream_;
+}
+
+void PseudoTcpChannel::OnChannelDestroyed(TransportChannel* channel) {
+ LOG_F(LS_INFO) << "(" << channel->name() << ")";
+ ASSERT(signal_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ ASSERT(channel == channel_);
+ signal_thread_->Clear(this, MSG_SI_DESTROYCHANNEL);
+ // When MSG_WK_PURGE is received, we know there will be no more messages from
+ // the worker thread.
+ worker_thread_->Clear(this, MSG_WK_CLOCK);
+ worker_thread_->Post(this, MSG_WK_PURGE);
+ session_ = NULL;
+ channel_ = NULL;
+ if ((stream_ != NULL)
+ && ((tcp_ == NULL) || (tcp_->State() != PseudoTcp::TCP_CLOSED)))
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, 0));
+ if (tcp_) {
+ tcp_->Close(true);
+ AdjustClock();
+ }
+ SignalChannelClosed(this);
+}
+
+void PseudoTcpChannel::OnSessionTerminate(Session* session) {
+ // When the session terminates before we even connected
+ CritScope lock(&cs_);
+ if (session_ != NULL && channel_ == NULL) {
+ ASSERT(session == session_);
+ ASSERT(worker_thread_ == NULL);
+ ASSERT(tcp_ == NULL);
+ LOG(LS_INFO) << "Destroying unconnected PseudoTcpChannel";
+ session_ = NULL;
+ if (stream_ != NULL)
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, -1));
+ }
+}
+
+//
+// Stream thread methods
+//
+
+StreamState PseudoTcpChannel::GetState() const {
+ ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!session_)
+ return SS_CLOSED;
+ if (!tcp_)
+ return SS_OPENING;
+ switch (tcp_->State()) {
+ case PseudoTcp::TCP_LISTEN:
+ case PseudoTcp::TCP_SYN_SENT:
+ case PseudoTcp::TCP_SYN_RECEIVED:
+ return SS_OPENING;
+ case PseudoTcp::TCP_ESTABLISHED:
+ return SS_OPEN;
+ case PseudoTcp::TCP_CLOSED:
+ default:
+ return SS_CLOSED;
+ }
+}
+
+StreamResult PseudoTcpChannel::Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error) {
+ ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!tcp_)
+ return SR_BLOCK;
+
+ stream_readable_ = false;
+ int result = tcp_->Recv(static_cast<char*>(buffer), buffer_len);
+ //LOG_F(LS_VERBOSE) << "Recv returned: " << result;
+ if (result > 0) {
+ if (read)
+ *read = result;
+ // PseudoTcp doesn't currently support repeated Readable signals. Simulate
+ // them here.
+ stream_readable_ = true;
+ if (!pending_read_event_) {
+ pending_read_event_ = true;
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ), true);
+ }
+ return SR_SUCCESS;
+ } else if (IsBlockingError(tcp_->GetError())) {
+ return SR_BLOCK;
+ } else {
+ if (error)
+ *error = tcp_->GetError();
+ return SR_ERROR;
+ }
+ // This spot is never reached.
+}
+
+StreamResult PseudoTcpChannel::Write(const void* data, size_t data_len,
+ size_t* written, int* error) {
+ ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!tcp_)
+ return SR_BLOCK;
+ int result = tcp_->Send(static_cast<const char*>(data), data_len);
+ //LOG_F(LS_VERBOSE) << "Send returned: " << result;
+ if (result > 0) {
+ if (written)
+ *written = result;
+ return SR_SUCCESS;
+ } else if (IsBlockingError(tcp_->GetError())) {
+ return SR_BLOCK;
+ } else {
+ if (error)
+ *error = tcp_->GetError();
+ return SR_ERROR;
+ }
+ // This spot is never reached.
+}
+
+void PseudoTcpChannel::Close() {
+ ASSERT(stream_ != NULL && stream_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ stream_ = NULL;
+ // Clear out any pending event notifications
+ stream_thread_->Clear(this, MSG_ST_EVENT);
+ if (tcp_) {
+ tcp_->Close(false);
+ AdjustClock();
+ } else {
+ CheckDestroy();
+ }
+}
+
+//
+// Worker thread methods
+//
+
+void PseudoTcpChannel::OnChannelWritableState(TransportChannel* channel) {
+ LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+ ASSERT(worker_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!channel_) {
+ LOG_F(LS_WARNING) << "NULL channel";
+ return;
+ }
+ ASSERT(channel == channel_);
+ if (!tcp_) {
+ LOG_F(LS_WARNING) << "NULL tcp";
+ return;
+ }
+ if (!ready_to_connect_ || !channel->writable())
+ return;
+
+ ready_to_connect_ = false;
+ tcp_->Connect();
+ AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelRead(TransportChannel* channel,
+ const char* data, size_t size) {
+ //LOG_F(LS_VERBOSE) << "(" << size << ")";
+ ASSERT(worker_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!channel_) {
+ LOG_F(LS_WARNING) << "NULL channel";
+ return;
+ }
+ ASSERT(channel == channel_);
+ if (!tcp_) {
+ LOG_F(LS_WARNING) << "NULL tcp";
+ return;
+ }
+ tcp_->NotifyPacket(data, size);
+ AdjustClock();
+}
+
+void PseudoTcpChannel::OnChannelConnectionChanged(TransportChannel* channel,
+ const SocketAddress& addr) {
+ LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+ ASSERT(worker_thread_->IsCurrent());
+ CritScope lock(&cs_);
+ if (!channel_) {
+ LOG_F(LS_WARNING) << "NULL channel";
+ return;
+ }
+ ASSERT(channel == channel_);
+ if (!tcp_) {
+ LOG_F(LS_WARNING) << "NULL tcp";
+ return;
+ }
+
+ uint16 mtu = 1280; // safe default
+ talk_base::scoped_ptr<Socket> mtu_socket(
+ worker_thread_->socketserver()->CreateSocket(SOCK_DGRAM));
+ if (mtu_socket->Connect(addr) < 0 ||
+ mtu_socket->EstimateMTU(&mtu) < 0) {
+ LOG_F(LS_WARNING) << "Failed to estimate MTU, error="
+ << mtu_socket->GetError();
+ }
+
+ LOG_F(LS_VERBOSE) << "Using MTU of " << mtu << " bytes";
+ tcp_->NotifyMTU(mtu);
+ AdjustClock();
+}
+
+void PseudoTcpChannel::OnTcpOpen(PseudoTcp* tcp) {
+ LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(worker_thread_->IsCurrent());
+ ASSERT(tcp == tcp_);
+ if (stream_) {
+ stream_readable_ = true;
+ pending_read_event_ = true;
+ stream_thread_->Post(this, MSG_ST_EVENT,
+ new EventData(SE_OPEN | SE_READ | SE_WRITE));
+ }
+}
+
+void PseudoTcpChannel::OnTcpReadable(PseudoTcp* tcp) {
+ //LOG_F(LS_VERBOSE);
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(worker_thread_->IsCurrent());
+ ASSERT(tcp == tcp_);
+ if (stream_) {
+ stream_readable_ = true;
+ if (!pending_read_event_) {
+ pending_read_event_ = true;
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_READ));
+ }
+ }
+}
+
+void PseudoTcpChannel::OnTcpWriteable(PseudoTcp* tcp) {
+ //LOG_F(LS_VERBOSE);
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(worker_thread_->IsCurrent());
+ ASSERT(tcp == tcp_);
+ if (stream_)
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_WRITE));
+}
+
+void PseudoTcpChannel::OnTcpClosed(PseudoTcp* tcp, uint32 nError) {
+ LOG_F(LS_VERBOSE) << "[" << channel_name_ << "]";
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(worker_thread_->IsCurrent());
+ ASSERT(tcp == tcp_);
+ if (stream_)
+ stream_thread_->Post(this, MSG_ST_EVENT, new EventData(SE_CLOSE, nError));
+}
+
+//
+// Multi-thread methods
+//
+
+void PseudoTcpChannel::OnMessage(Message* pmsg) {
+ if (pmsg->message_id == MSG_WK_CLOCK) {
+
+ ASSERT(worker_thread_->IsCurrent());
+ //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_WK_CLOCK)";
+ CritScope lock(&cs_);
+ if (tcp_) {
+ tcp_->NotifyClock(PseudoTcp::Now());
+ AdjustClock(false);
+ }
+
+ } else if (pmsg->message_id == MSG_WK_PURGE) {
+
+ ASSERT(worker_thread_->IsCurrent());
+ LOG_F(LS_INFO) << "(MSG_WK_PURGE)";
+ // At this point, we know there are no additional worker thread messages.
+ CritScope lock(&cs_);
+ ASSERT(NULL == session_);
+ ASSERT(NULL == channel_);
+ worker_thread_ = NULL;
+ CheckDestroy();
+
+ } else if (pmsg->message_id == MSG_ST_EVENT) {
+
+ ASSERT(stream_thread_->IsCurrent());
+ //LOG(LS_INFO) << "PseudoTcpChannel::OnMessage(MSG_ST_EVENT, "
+ // << data->event << ", " << data->error << ")";
+ ASSERT(stream_ != NULL);
+ EventData* data = static_cast<EventData*>(pmsg->pdata);
+ if (data->event & SE_READ) {
+ CritScope lock(&cs_);
+ pending_read_event_ = false;
+ }
+ stream_->SignalEvent(stream_, data->event, data->error);
+ delete data;
+
+ } else if (pmsg->message_id == MSG_SI_DESTROYCHANNEL) {
+
+ ASSERT(signal_thread_->IsCurrent());
+ LOG_F(LS_INFO) << "(MSG_SI_DESTROYCHANNEL)";
+ ASSERT(session_ != NULL);
+ ASSERT(channel_ != NULL);
+ session_->DestroyChannel(content_name_, channel_->name());
+
+ } else if (pmsg->message_id == MSG_SI_DESTROY) {
+
+ ASSERT(signal_thread_->IsCurrent());
+ LOG_F(LS_INFO) << "(MSG_SI_DESTROY)";
+ // The message queue is empty, so it is safe to destroy ourselves.
+ delete this;
+
+ } else {
+ ASSERT(false);
+ }
+}
+
+IPseudoTcpNotify::WriteResult PseudoTcpChannel::TcpWritePacket(
+ PseudoTcp* tcp, const char* buffer, size_t len) {
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(tcp == tcp_);
+ ASSERT(NULL != channel_);
+ int sent = channel_->SendPacket(buffer, len);
+ if (sent > 0) {
+ //LOG_F(LS_VERBOSE) << "(" << sent << ") Sent";
+ return IPseudoTcpNotify::WR_SUCCESS;
+ } else if (IsBlockingError(channel_->GetError())) {
+ LOG_F(LS_VERBOSE) << "Blocking";
+ return IPseudoTcpNotify::WR_SUCCESS;
+ } else if (channel_->GetError() == EMSGSIZE) {
+ LOG_F(LS_ERROR) << "EMSGSIZE";
+ return IPseudoTcpNotify::WR_TOO_LARGE;
+ } else {
+ PLOG(LS_ERROR, channel_->GetError()) << "PseudoTcpChannel::TcpWritePacket";
+ ASSERT(false);
+ return IPseudoTcpNotify::WR_FAIL;
+ }
+}
+
+void PseudoTcpChannel::AdjustClock(bool clear) {
+ ASSERT(cs_.CurrentThreadIsOwner());
+ ASSERT(NULL != tcp_);
+
+ long timeout = 0;
+ if (tcp_->GetNextClock(PseudoTcp::Now(), timeout)) {
+ ASSERT(NULL != channel_);
+ // Reset the next clock, by clearing the old and setting a new one.
+ if (clear)
+ worker_thread_->Clear(this, MSG_WK_CLOCK);
+ worker_thread_->PostDelayed(_max(timeout, 0L), this, MSG_WK_CLOCK);
+ return;
+ }
+
+ delete tcp_;
+ tcp_ = NULL;
+ ready_to_connect_ = false;
+
+ if (channel_) {
+ // If TCP has failed, no need for channel_ anymore
+ signal_thread_->Post(this, MSG_SI_DESTROYCHANNEL);
+ }
+}
+
+void PseudoTcpChannel::CheckDestroy() {
+ ASSERT(cs_.CurrentThreadIsOwner());
+ if ((worker_thread_ != NULL) || (stream_ != NULL))
+ return;
+ signal_thread_->Post(this, MSG_SI_DESTROY);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// PseudoTcpChannel::InternalStream
+///////////////////////////////////////////////////////////////////////////////
+
+PseudoTcpChannel::InternalStream::InternalStream(PseudoTcpChannel* parent)
+ : parent_(parent) {
+}
+
+PseudoTcpChannel::InternalStream::~InternalStream() {
+ Close();
+}
+
+StreamState PseudoTcpChannel::InternalStream::GetState() const {
+ if (!parent_)
+ return SS_CLOSED;
+ return parent_->GetState();
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Read(
+ void* buffer, size_t buffer_len, size_t* read, int* error) {
+ if (!parent_) {
+ if (error)
+ *error = ENOTCONN;
+ return SR_ERROR;
+ }
+ return parent_->Read(buffer, buffer_len, read, error);
+}
+
+StreamResult PseudoTcpChannel::InternalStream::Write(
+ const void* data, size_t data_len, size_t* written, int* error) {
+ if (!parent_) {
+ if (error)
+ *error = ENOTCONN;
+ return SR_ERROR;
+ }
+ return parent_->Write(data, data_len, written, error);
+}
+
+void PseudoTcpChannel::InternalStream::Close() {
+ if (!parent_)
+ return;
+ parent_->Close();
+ parent_ = NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/talk/session/tunnel/pseudotcpchannel.h b/talk/session/tunnel/pseudotcpchannel.h
new file mode 100644
index 0000000..fbcb611
--- /dev/null
+++ b/talk/session/tunnel/pseudotcpchannel.h
@@ -0,0 +1,129 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 __PSEUDOTCPCHANNEL_H__
+#define __PSEUDOTCPCHANNEL_H__
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/messagequeue.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+
+namespace talk_base {
+class Thread;
+}
+
+namespace cricket {
+
+class TransportChannel;
+
+///////////////////////////////////////////////////////////////////////////////
+// ChannelStream
+// Note: The lifetime of TunnelSession is complicated. It needs to survive
+// until the following three conditions are true:
+// 1) TunnelStream has called Close (tracked via non-null stream_)
+// 2) PseudoTcp has completed (tracked via non-null tcp_)
+// 3) Session has been destroyed (tracked via non-null session_)
+// This is accomplished by calling CheckDestroy after these indicators change.
+///////////////////////////////////////////////////////////////////////////////
+// TunnelStream
+// Note: Because TunnelStream provides a stream interface, it's lifetime is
+// controlled by the owner of the stream pointer. As a result, we must support
+// both the TunnelSession disappearing before TunnelStream, and vice versa.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel
+ : public IPseudoTcpNotify,
+ public talk_base::MessageHandler,
+ public sigslot::has_slots<> {
+public:
+ // Signal thread methods
+ PseudoTcpChannel(talk_base::Thread* stream_thread,
+ Session* session);
+
+ bool Connect(const std::string& content_name,
+ const std::string& channel_name);
+ talk_base::StreamInterface* GetStream();
+
+ sigslot::signal1<PseudoTcpChannel*> SignalChannelClosed;
+
+ void OnSessionTerminate(Session* session);
+
+private:
+ class InternalStream;
+ friend class InternalStream;
+
+ virtual ~PseudoTcpChannel();
+
+ // Stream thread methods
+ talk_base::StreamState GetState() const;
+ talk_base::StreamResult Read(void* buffer, size_t buffer_len,
+ size_t* read, int* error);
+ talk_base::StreamResult Write(const void* data, size_t data_len,
+ size_t* written, int* error);
+ void Close();
+
+ // Multi-thread methods
+ void OnMessage(talk_base::Message* pmsg);
+ void AdjustClock(bool clear = true);
+ void CheckDestroy();
+
+ // Signal thread methods
+ void OnChannelDestroyed(TransportChannel* channel);
+
+ // Worker thread methods
+ void OnChannelWritableState(TransportChannel* channel);
+ void OnChannelRead(TransportChannel* channel, const char* data, size_t size);
+ void OnChannelConnectionChanged(TransportChannel* channel,
+ const talk_base::SocketAddress& addr);
+
+ virtual void OnTcpOpen(PseudoTcp* ptcp);
+ virtual void OnTcpReadable(PseudoTcp* ptcp);
+ virtual void OnTcpWriteable(PseudoTcp* ptcp);
+ virtual void OnTcpClosed(PseudoTcp* ptcp, uint32 nError);
+ virtual IPseudoTcpNotify::WriteResult TcpWritePacket(PseudoTcp* tcp,
+ const char* buffer,
+ size_t len);
+
+ talk_base::Thread* signal_thread_, * worker_thread_, * stream_thread_;
+ Session* session_;
+ TransportChannel* channel_;
+ std::string content_name_;
+ std::string channel_name_;
+ PseudoTcp* tcp_;
+ InternalStream* stream_;
+ bool stream_readable_, pending_read_event_;
+ bool ready_to_connect_;
+ mutable talk_base::CriticalSection cs_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
+
+#endif // __PSEUDOTCPCHANNEL_H__
diff --git a/talk/session/tunnel/securetunnelsessionclient.cc b/talk/session/tunnel/securetunnelsessionclient.cc
new file mode 100644
index 0000000..84ec5e0
--- /dev/null
+++ b/talk/session/tunnel/securetunnelsessionclient.cc
@@ -0,0 +1,382 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession implementation.
+
+#include "talk/session/tunnel/securetunnelsessionclient.h"
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/session/tunnel/pseudotcpchannel.h"
+
+namespace cricket {
+
+// XML elements and namespaces for XMPP stanzas used in content exchanges.
+
+const std::string NS_SECURE_TUNNEL("http://www.google.com/talk/securetunnel");
+const buzz::QName QN_SECURE_TUNNEL_DESCRIPTION(NS_SECURE_TUNNEL,
+ "description");
+const buzz::QName QN_SECURE_TUNNEL_TYPE(NS_SECURE_TUNNEL, "type");
+const buzz::QName QN_SECURE_TUNNEL_CLIENT_CERT(NS_SECURE_TUNNEL,
+ "client-cert");
+const buzz::QName QN_SECURE_TUNNEL_SERVER_CERT(NS_SECURE_TUNNEL,
+ "server-cert");
+const std::string CN_SECURE_TUNNEL("securetunnel");
+
+// SecureTunnelContentDescription
+
+// TunnelContentDescription is extended to hold string forms of the
+// client and server certificate, PEM encoded.
+
+struct SecureTunnelContentDescription : public ContentDescription {
+ std::string description;
+ std::string client_pem_certificate;
+ std::string server_pem_certificate;
+
+ SecureTunnelContentDescription(const std::string& desc,
+ const std::string& client_pem_cert,
+ const std::string& server_pem_cert)
+ : description(desc),
+ client_pem_certificate(client_pem_cert),
+ server_pem_certificate(server_pem_cert) {
+ }
+};
+
+// SecureTunnelSessionClient
+
+SecureTunnelSessionClient::SecureTunnelSessionClient(
+ const buzz::Jid& jid, SessionManager* manager)
+ : TunnelSessionClient(jid, manager, NS_SECURE_TUNNEL) {
+}
+
+void SecureTunnelSessionClient::SetIdentity(talk_base::SSLIdentity* identity) {
+ ASSERT(identity_.get() == NULL);
+ identity_.reset(identity);
+}
+
+bool SecureTunnelSessionClient::GenerateIdentity() {
+ ASSERT(identity_.get() == NULL);
+ identity_.reset(talk_base::SSLIdentity::Generate(
+ // The name on the certificate does not matter: the peer will
+ // make sure the cert it gets during SSL negotiation matches the
+ // one it got from XMPP. It would be neat to put something
+ // recognizable in there such as the JID, except this will show
+ // in clear during the SSL negotiation and so it could be a
+ // privacy issue. Specifying an empty string here causes
+ // it to use a random string.
+#ifdef _DEBUG
+ jid().Str()
+#else
+ ""
+#endif
+ ));
+ if (identity_.get() == NULL) {
+ LOG(LS_ERROR) << "Failed to generate SSL identity";
+ return false;
+ }
+ return true;
+}
+
+talk_base::SSLIdentity& SecureTunnelSessionClient::GetIdentity() const {
+ ASSERT(identity_.get() != NULL);
+ return *identity_;
+}
+
+// Parses a certificate from a PEM encoded string.
+// Returns NULL on failure.
+// The caller is responsible for freeing the returned object.
+static talk_base::SSLCertificate* ParseCertificate(
+ const std::string& pem_cert) {
+ if (pem_cert.empty())
+ return NULL;
+ return talk_base::SSLCertificate::FromPEMString(pem_cert, NULL);
+}
+
+TunnelSession* SecureTunnelSessionClient::MakeTunnelSession(
+ Session* session, talk_base::Thread* stream_thread,
+ TunnelSessionRole role) {
+ return new SecureTunnelSession(this, session, stream_thread, role);
+}
+
+bool FindSecureTunnelContent(const cricket::SessionDescription* sdesc,
+ std::string* name,
+ const SecureTunnelContentDescription** content) {
+ const ContentInfo* cinfo = sdesc->FirstContentByType(NS_SECURE_TUNNEL);
+ if (cinfo == NULL)
+ return false;
+
+ *name = cinfo->name;
+ *content = static_cast<const SecureTunnelContentDescription*>(
+ cinfo->description);
+ return true;
+}
+
+void SecureTunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+ Session *session) {
+ std::string content_name;
+ const SecureTunnelContentDescription* content = NULL;
+ if (!FindSecureTunnelContent(session->remote_description(),
+ &content_name, &content)) {
+ ASSERT(false);
+ }
+
+ // Validate the certificate
+ talk_base::scoped_ptr<talk_base::SSLCertificate> peer_cert(
+ ParseCertificate(content->client_pem_certificate));
+ if (peer_cert.get() == NULL) {
+ LOG(LS_ERROR)
+ << "Rejecting incoming secure tunnel with invalid cetificate";
+ DeclineTunnel(session);
+ return;
+ }
+ // If there were a convenient place we could have cached the
+ // peer_cert so as not to have to parse it a second time when
+ // configuring the tunnel.
+ SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+// The XML representation of a session initiation request (XMPP IQ),
+// containing the initiator's SecureTunnelContentDescription,
+// looks something like this:
+// <iq from="INITIATOR@gmail.com/pcpE101B7F4"
+// to="RECIPIENT@gmail.com/pcp8B87F0A3"
+// type="set" id="3">
+// <session xmlns="http://www.google.com/session"
+// type="initiate" id="2508605813"
+// initiator="INITIATOR@gmail.com/pcpE101B7F4">
+// <description xmlns="http://www.google.com/talk/securetunnel">
+// <type>send:filename</type>
+// <client-cert>
+// -----BEGIN CERTIFICATE-----
+// INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+// -----END CERTIFICATE-----
+// </client-cert>
+// </description>
+// <transport xmlns="http://www.google.com/transport/p2p"/>
+// </session>
+// </iq>
+
+// The session accept iq, containing the recipient's certificate and
+// echoing the initiator's certificate, looks something like this:
+// <iq from="RECIPIENT@gmail.com/pcpE101B7F4"
+// to="INITIATOR@gmail.com/pcpE101B7F4"
+// type="set" id="5">
+// <session xmlns="http://www.google.com/session"
+// type="accept" id="2508605813"
+// initiator="sdoyon911@gmail.com/pcpE101B7F4">
+// <description xmlns="http://www.google.com/talk/securetunnel">
+// <type>send:FILENAME</type>
+// <client-cert>
+// -----BEGIN CERTIFICATE-----
+// INITIATOR'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+// -----END CERTIFICATE-----
+// </client-cert>
+// <server-cert>
+// -----BEGIN CERTIFICATE-----
+// RECIPIENT'S CERTIFICATE IN PERM FORMAT (ASCII GIBBERISH)
+// -----END CERTIFICATE-----
+// </server-cert>
+// </description>
+// </session>
+// </iq>
+
+
+bool SecureTunnelSessionClient::ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ const buzz::XmlElement* type_elem = elem->FirstNamed(QN_SECURE_TUNNEL_TYPE);
+
+ if (type_elem == NULL)
+ // Missing mandatory XML element.
+ return false;
+
+ // Here we consider the certificate components to be optional. In
+ // practice the client certificate is always present, and the server
+ // certificate is initially missing from the session description
+ // sent during session initiation. OnAccept() will enforce that we
+ // have a certificate for our peer.
+ const buzz::XmlElement* client_cert_elem =
+ elem->FirstNamed(QN_SECURE_TUNNEL_CLIENT_CERT);
+ const buzz::XmlElement* server_cert_elem =
+ elem->FirstNamed(QN_SECURE_TUNNEL_SERVER_CERT);
+ *content = new SecureTunnelContentDescription(
+ type_elem->BodyText(),
+ client_cert_elem ? client_cert_elem->BodyText() : "",
+ server_cert_elem ? server_cert_elem->BodyText() : "");
+ return true;
+}
+
+bool SecureTunnelSessionClient::WriteContent(
+ SignalingProtocol protocol, const ContentDescription* untyped_content,
+ buzz::XmlElement** elem, WriteError* error) {
+ const SecureTunnelContentDescription* content =
+ static_cast<const SecureTunnelContentDescription*>(untyped_content);
+
+ buzz::XmlElement* root =
+ new buzz::XmlElement(QN_SECURE_TUNNEL_DESCRIPTION, true);
+ buzz::XmlElement* type_elem = new buzz::XmlElement(QN_SECURE_TUNNEL_TYPE);
+ type_elem->SetBodyText(content->description);
+ root->AddElement(type_elem);
+ if (!content->client_pem_certificate.empty()) {
+ buzz::XmlElement* client_cert_elem =
+ new buzz::XmlElement(QN_SECURE_TUNNEL_CLIENT_CERT);
+ client_cert_elem->SetBodyText(content->client_pem_certificate);
+ root->AddElement(client_cert_elem);
+ }
+ if (!content->server_pem_certificate.empty()) {
+ buzz::XmlElement* server_cert_elem =
+ new buzz::XmlElement(QN_SECURE_TUNNEL_SERVER_CERT);
+ server_cert_elem->SetBodyText(content->server_pem_certificate);
+ root->AddElement(server_cert_elem);
+ }
+ *elem = root;
+ return true;
+}
+
+SessionDescription* NewSecureTunnelSessionDescription(
+ const std::string& content_name, const ContentDescription* content) {
+ SessionDescription* sdesc = new SessionDescription();
+ sdesc->AddContent(content_name, NS_SECURE_TUNNEL, content);
+ return sdesc;
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateOffer(
+ const buzz::Jid &jid, const std::string &description) {
+ // We are the initiator so we are the client. Put our cert into the
+ // description.
+ std::string pem_cert = GetIdentity().certificate().ToPEMString();
+ return NewSecureTunnelSessionDescription(
+ CN_SECURE_TUNNEL,
+ new SecureTunnelContentDescription(description, pem_cert, ""));
+}
+
+SessionDescription* SecureTunnelSessionClient::CreateAnswer(
+ const SessionDescription* offer) {
+ std::string content_name;
+ const SecureTunnelContentDescription* offer_tunnel = NULL;
+ if (!FindSecureTunnelContent(offer, &content_name, &offer_tunnel))
+ return NULL;
+
+ // We are accepting a session request. We need to add our cert, the
+ // server cert, into the description. The client cert was validated
+ // in OnIncomingTunnel().
+ ASSERT(!offer_tunnel->client_pem_certificate.empty());
+ return NewSecureTunnelSessionDescription(
+ content_name,
+ new SecureTunnelContentDescription(
+ offer_tunnel->description,
+ offer_tunnel->client_pem_certificate,
+ GetIdentity().certificate().ToPEMString()));
+}
+
+// SecureTunnelSession
+
+SecureTunnelSession::SecureTunnelSession(
+ SecureTunnelSessionClient* client, Session* session,
+ talk_base::Thread* stream_thread, TunnelSessionRole role)
+ : TunnelSession(client, session, stream_thread),
+ role_(role) {
+}
+
+talk_base::StreamInterface* SecureTunnelSession::MakeSecureStream(
+ talk_base::StreamInterface* stream) {
+ talk_base::SSLStreamAdapter* ssl_stream =
+ talk_base::SSLStreamAdapter::Create(stream);
+ talk_base::SSLIdentity* identity =
+ static_cast<SecureTunnelSessionClient*>(client_)->
+ GetIdentity().GetReference();
+ ssl_stream->SetIdentity(identity);
+ if (role_ == RESPONDER)
+ ssl_stream->SetServerRole();
+ ssl_stream->StartSSLWithPeer();
+
+ // SSL negotiation will start on the stream as soon as it
+ // opens. However our SSLStreamAdapter still hasn't been told what
+ // certificate to allow for our peer. If we are the initiator, we do
+ // not have the peer's certificate yet: we will obtain it from the
+ // session accept message which we will receive later (see
+ // OnAccept()). We won't Connect() the PseudoTcpChannel until we get
+ // that, so the stream will stay closed until then. Keep a handle
+ // on the streem so we can configure the peer certificate later.
+ ssl_stream_reference_.reset(new talk_base::StreamReference(ssl_stream));
+ return ssl_stream_reference_->NewReference();
+}
+
+talk_base::StreamInterface* SecureTunnelSession::GetStream() {
+ ASSERT(channel_ != NULL);
+ ASSERT(ssl_stream_reference_.get() == NULL);
+ return MakeSecureStream(channel_->GetStream());
+}
+
+void SecureTunnelSession::OnAccept() {
+ // We have either sent or received a session accept: it's time to
+ // connect the tunnel. First we must set the peer certificate.
+ ASSERT(channel_ != NULL);
+ ASSERT(session_ != NULL);
+ std::string content_name;
+ const SecureTunnelContentDescription* remote_tunnel = NULL;
+ if (!FindSecureTunnelContent(session_->remote_description(),
+ &content_name, &remote_tunnel)) {
+ session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+ return;
+ }
+
+ const std::string& cert_pem =
+ role_ == INITIATOR ? remote_tunnel->server_pem_certificate :
+ remote_tunnel->client_pem_certificate;
+ talk_base::SSLCertificate* peer_cert =
+ ParseCertificate(cert_pem);
+ if (peer_cert == NULL) {
+ ASSERT(role_ == INITIATOR); // when RESPONDER we validated it earlier
+ LOG(LS_ERROR)
+ << "Rejecting secure tunnel accept with invalid cetificate";
+ session_->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+ return;
+ }
+ ASSERT(ssl_stream_reference_.get() != NULL);
+ talk_base::SSLStreamAdapter* ssl_stream =
+ static_cast<talk_base::SSLStreamAdapter*>(
+ ssl_stream_reference_->GetStream());
+ ssl_stream->SetPeerCertificate(peer_cert); // pass ownership of certificate.
+ // We no longer need our handle to the ssl stream.
+ ssl_stream_reference_.reset();
+ LOG(LS_INFO) << "Connecting tunnel";
+ // This will try to connect the PseudoTcpChannel. If and when that
+ // succeeds, then ssl negotiation will take place, and when that
+ // succeeds, the tunnel stream will finally open.
+ VERIFY(channel_->Connect(content_name, "tcp"));
+}
+
+} // namespace cricket
diff --git a/talk/session/tunnel/securetunnelsessionclient.h b/talk/session/tunnel/securetunnelsessionclient.h
new file mode 100644
index 0000000..cb9a99c
--- /dev/null
+++ b/talk/session/tunnel/securetunnelsessionclient.h
@@ -0,0 +1,165 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// SecureTunnelSessionClient and SecureTunnelSession.
+// SecureTunnelSessionClient extends TunnelSessionClient to exchange
+// certificates as part of the session description.
+// SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+#ifndef TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+#define TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
+
+#include <string>
+
+#include "talk/base/sslidentity.h"
+#include "talk/base/sslstreamadapter.h"
+#include "talk/session/tunnel/tunnelsessionclient.h"
+
+namespace cricket {
+
+class SecureTunnelSession; // below
+
+// SecureTunnelSessionClient
+
+// This TunnelSessionClient establishes secure tunnels protected by
+// SSL/TLS. The PseudoTcpChannel stream is wrapped with an
+// SSLStreamAdapter. An SSLIdentity must be set or generated.
+//
+// The TunnelContentDescription is extended to include the client and
+// server certificates. The initiator acts as the client. The session
+// initiate stanza carries a description that contains the client's
+// certificate, and the session accept response's description has the
+// server certificate added to it.
+
+class SecureTunnelSessionClient : public TunnelSessionClient {
+ public:
+ // The jid is used as the name for sessions for outgoing tunnels.
+ // manager is the SessionManager to which we register this client
+ // and its sessions.
+ SecureTunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+
+ // Configures this client to use a preexisting SSLIdentity.
+ // The client takes ownership of the identity object.
+ // Use either SetIdentity or GenerateIdentity, and only once.
+ void SetIdentity(talk_base::SSLIdentity* identity);
+
+ // Generates an identity from nothing.
+ // Returns true if generation was successful.
+ // Use either SetIdentity or GenerateIdentity, and only once.
+ bool GenerateIdentity();
+
+ // Returns our identity for SSL purposes, as either set by
+ // SetIdentity() or generated by GenerateIdentity(). Call this
+ // method only after our identity has been successfully established
+ // by one of those methods.
+ talk_base::SSLIdentity& GetIdentity() const;
+
+ // Inherited methods
+ virtual void OnIncomingTunnel(const buzz::Jid& jid, Session *session);
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error);
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error);
+ virtual SessionDescription* CreateOffer(
+ const buzz::Jid &jid, const std::string &description);
+ virtual SessionDescription* CreateAnswer(
+ const SessionDescription* offer);
+
+ protected:
+ virtual TunnelSession* MakeTunnelSession(
+ Session* session, talk_base::Thread* stream_thread,
+ TunnelSessionRole role);
+
+ private:
+ // Our identity (key and certificate) for SSL purposes. The
+ // certificate part will be communicated within the session
+ // description. The identity will be passed to the SSLStreamAdapter
+ // and used for SSL authentication.
+ talk_base::scoped_ptr<talk_base::SSLIdentity> identity_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSessionClient);
+};
+
+// SecureTunnelSession:
+// A TunnelSession represents one session for one client. It
+// provides the actual tunnel stream and handles state changes.
+// A SecureTunnelSession is a TunnelSession that wraps the underlying
+// tunnel stream into an SSLStreamAdapter.
+
+class SecureTunnelSession : public TunnelSession {
+ public:
+ // This TunnelSession will tie together the given client and session.
+ // stream_thread is passed to the PseudoTCPChannel: it's the thread
+ // designated to interact with the tunnel stream.
+ // role is either INITIATOR or RESPONDER, depending on who is
+ // initiating the session.
+ SecureTunnelSession(SecureTunnelSessionClient* client, Session* session,
+ talk_base::Thread* stream_thread,
+ TunnelSessionRole role);
+
+ // Returns the stream that implements the actual P2P tunnel.
+ // This may be called only once. Caller is responsible for freeing
+ // the returned object.
+ virtual talk_base::StreamInterface* GetStream();
+
+ protected:
+ // Inherited method: callback on accepting a session.
+ virtual void OnAccept();
+
+ // Helper method for GetStream() that Instantiates the
+ // SSLStreamAdapter to wrap the PseudoTcpChannel's stream, and
+ // configures it with our identity and role.
+ talk_base::StreamInterface* MakeSecureStream(
+ talk_base::StreamInterface* stream);
+
+ // Our role in requesting the tunnel: INITIATOR or
+ // RESPONDER. Translates to our role in SSL negotiation:
+ // respectively client or server. Also indicates which slot of the
+ // SecureTunnelContentDescription our cert goes into: client-cert or
+ // server-cert respectively.
+ TunnelSessionRole role_;
+
+ // This is the stream representing the usable tunnel endpoint. It's
+ // a StreamReference wrapping the SSLStreamAdapter instance, which
+ // further wraps a PseudoTcpChannel::InternalStream. The
+ // StreamReference is because in the case of CreateTunnel(), the
+ // stream endpoint is returned early, but we need to keep a handle
+ // on it so we can setup the peer certificate when we receive it
+ // later.
+ talk_base::scoped_ptr<talk_base::StreamReference> ssl_stream_reference_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(SecureTunnelSession);
+};
+
+} // namespace cricket
+
+#endif // TALK_SESSION_TUNNEL_SECURETUNNELSESSIONCLIENT_H_
diff --git a/talk/session/tunnel/tunnelsessionclient.cc b/talk/session/tunnel/tunnelsessionclient.cc
new file mode 100644
index 0000000..13d5c10
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient.cc
@@ -0,0 +1,398 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "talk/base/basicdefs.h"
+#include "talk/base/basictypes.h"
+#include "talk/base/common.h"
+#include "talk/base/helpers.h"
+#include "talk/base/logging.h"
+#include "talk/base/stringutils.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/transportchannel.h"
+#include "talk/xmllite/xmlelement.h"
+#include "pseudotcpchannel.h"
+#include "tunnelsessionclient.h"
+
+namespace cricket {
+
+const std::string NS_TUNNEL("http://www.google.com/talk/tunnel");
+const buzz::QName QN_TUNNEL_DESCRIPTION(NS_TUNNEL, "description");
+const buzz::QName QN_TUNNEL_TYPE(NS_TUNNEL, "type");
+const std::string CN_TUNNEL("tunnel");
+
+enum {
+ MSG_CLOCK = 1,
+ MSG_DESTROY,
+ MSG_TERMINATE,
+ MSG_EVENT,
+ MSG_CREATE_TUNNEL,
+};
+
+struct EventData : public talk_base::MessageData {
+ int event, error;
+ EventData(int ev, int err = 0) : event(ev), error(err) { }
+};
+
+struct CreateTunnelData : public talk_base::MessageData {
+ buzz::Jid jid;
+ std::string description;
+ talk_base::Thread* thread;
+ talk_base::StreamInterface* stream;
+};
+
+extern const talk_base::ConstantLabel SESSION_STATES[];
+
+const talk_base::ConstantLabel SESSION_STATES[] = {
+ KLABEL(Session::STATE_INIT),
+ KLABEL(Session::STATE_SENTINITIATE),
+ KLABEL(Session::STATE_RECEIVEDINITIATE),
+ KLABEL(Session::STATE_SENTACCEPT),
+ KLABEL(Session::STATE_RECEIVEDACCEPT),
+ KLABEL(Session::STATE_SENTMODIFY),
+ KLABEL(Session::STATE_RECEIVEDMODIFY),
+ KLABEL(Session::STATE_SENTREJECT),
+ KLABEL(Session::STATE_RECEIVEDREJECT),
+ KLABEL(Session::STATE_SENTREDIRECT),
+ KLABEL(Session::STATE_SENTTERMINATE),
+ KLABEL(Session::STATE_RECEIVEDTERMINATE),
+ KLABEL(Session::STATE_INPROGRESS),
+ KLABEL(Session::STATE_DEINIT),
+ LASTLABEL
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelContentDescription
+///////////////////////////////////////////////////////////////////////////////
+
+struct TunnelContentDescription : public ContentDescription {
+ std::string description;
+
+ TunnelContentDescription(const std::string& desc) : description(desc) { }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClientBase
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClientBase::TunnelSessionClientBase(const buzz::Jid& jid,
+ SessionManager* manager, const std::string &ns)
+ : jid_(jid), session_manager_(manager), namespace_(ns), shutdown_(false) {
+ session_manager_->AddClient(namespace_, this);
+}
+
+TunnelSessionClientBase::~TunnelSessionClientBase() {
+ shutdown_ = true;
+ for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+ it != sessions_.end();
+ ++it) {
+ Session* session = (*it)->ReleaseSession(true);
+ session_manager_->DestroySession(session);
+ }
+ session_manager_->RemoveClient(namespace_);
+}
+
+void TunnelSessionClientBase::OnSessionCreate(Session* session, bool received) {
+ LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionCreate: received="
+ << received;
+ ASSERT(session_manager_->signaling_thread()->IsCurrent());
+ if (received)
+ sessions_.push_back(
+ MakeTunnelSession(session, talk_base::Thread::Current(), RESPONDER));
+}
+
+void TunnelSessionClientBase::OnSessionDestroy(Session* session) {
+ LOG(LS_INFO) << "TunnelSessionClientBase::OnSessionDestroy";
+ ASSERT(session_manager_->signaling_thread()->IsCurrent());
+ if (shutdown_)
+ return;
+ for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+ it != sessions_.end();
+ ++it) {
+ if ((*it)->HasSession(session)) {
+ VERIFY((*it)->ReleaseSession(false) == session);
+ sessions_.erase(it);
+ return;
+ }
+ }
+}
+
+talk_base::StreamInterface* TunnelSessionClientBase::CreateTunnel(
+ const buzz::Jid& to, const std::string& description) {
+ // Valid from any thread
+ CreateTunnelData data;
+ data.jid = to;
+ data.description = description;
+ data.thread = talk_base::Thread::Current();
+ session_manager_->signaling_thread()->Send(this, MSG_CREATE_TUNNEL, &data);
+ return data.stream;
+}
+
+talk_base::StreamInterface* TunnelSessionClientBase::AcceptTunnel(
+ Session* session) {
+ ASSERT(session_manager_->signaling_thread()->IsCurrent());
+ TunnelSession* tunnel = NULL;
+ for (std::vector<TunnelSession*>::iterator it = sessions_.begin();
+ it != sessions_.end();
+ ++it) {
+ if ((*it)->HasSession(session)) {
+ tunnel = *it;
+ break;
+ }
+ }
+ ASSERT(tunnel != NULL);
+
+ SessionDescription* answer = CreateAnswer(session->remote_description());
+ if (answer == NULL)
+ return NULL;
+
+ session->Accept(answer);
+ return tunnel->GetStream();
+}
+
+void TunnelSessionClientBase::DeclineTunnel(Session* session) {
+ ASSERT(session_manager_->signaling_thread()->IsCurrent());
+ session->Reject(STR_TERMINATE_DECLINE);
+}
+
+void TunnelSessionClientBase::OnMessage(talk_base::Message* pmsg) {
+ if (pmsg->message_id == MSG_CREATE_TUNNEL) {
+ ASSERT(session_manager_->signaling_thread()->IsCurrent());
+ CreateTunnelData* data = static_cast<CreateTunnelData*>(pmsg->pdata);
+ Session* session = session_manager_->CreateSession(jid_.Str(), namespace_);
+ TunnelSession* tunnel = MakeTunnelSession(session, data->thread,
+ INITIATOR);
+ sessions_.push_back(tunnel);
+ SessionDescription* offer = CreateOffer(data->jid, data->description);
+ session->Initiate(data->jid.Str(), offer);
+ data->stream = tunnel->GetStream();
+ }
+}
+
+TunnelSession* TunnelSessionClientBase::MakeTunnelSession(
+ Session* session, talk_base::Thread* stream_thread,
+ TunnelSessionRole /*role*/) {
+ return new TunnelSession(this, session, stream_thread);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+ SessionManager* manager,
+ const std::string &ns)
+ : TunnelSessionClientBase(jid, manager, ns) {
+}
+
+TunnelSessionClient::TunnelSessionClient(const buzz::Jid& jid,
+ SessionManager* manager)
+ : TunnelSessionClientBase(jid, manager, NS_TUNNEL) {
+}
+
+TunnelSessionClient::~TunnelSessionClient() {
+}
+
+
+bool TunnelSessionClient::ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error) {
+ if (const buzz::XmlElement* type_elem = elem->FirstNamed(QN_TUNNEL_TYPE)) {
+ *content = new TunnelContentDescription(type_elem->BodyText());
+ return true;
+ }
+ return false;
+}
+
+bool TunnelSessionClient::WriteContent(
+ SignalingProtocol protocol,
+ const ContentDescription* untyped_content,
+ buzz::XmlElement** elem, WriteError* error) {
+ const TunnelContentDescription* content =
+ static_cast<const TunnelContentDescription*>(untyped_content);
+
+ buzz::XmlElement* root = new buzz::XmlElement(QN_TUNNEL_DESCRIPTION, true);
+ buzz::XmlElement* type_elem = new buzz::XmlElement(QN_TUNNEL_TYPE);
+ type_elem->SetBodyText(content->description);
+ root->AddElement(type_elem);
+ *elem = root;
+ return true;
+}
+
+SessionDescription* NewTunnelSessionDescription(
+ const std::string& content_name, const ContentDescription* content) {
+ SessionDescription* sdesc = new SessionDescription();
+ sdesc->AddContent(content_name, NS_TUNNEL, content);
+ return sdesc;
+}
+
+bool FindTunnelContent(const cricket::SessionDescription* sdesc,
+ std::string* name,
+ const TunnelContentDescription** content) {
+ const ContentInfo* cinfo = sdesc->FirstContentByType(NS_TUNNEL);
+ if (cinfo == NULL)
+ return false;
+
+ *name = cinfo->name;
+ *content = static_cast<const TunnelContentDescription*>(
+ cinfo->description);
+ return true;
+}
+
+void TunnelSessionClient::OnIncomingTunnel(const buzz::Jid &jid,
+ Session *session) {
+ std::string content_name;
+ const TunnelContentDescription* content = NULL;
+ if (!FindTunnelContent(session->remote_description(),
+ &content_name, &content)) {
+ session->Reject(STR_TERMINATE_INCOMPATIBLE_PARAMETERS);
+ return;
+ }
+
+ SignalIncomingTunnel(this, jid, content->description, session);
+}
+
+SessionDescription* TunnelSessionClient::CreateOffer(
+ const buzz::Jid &jid, const std::string &description) {
+ return NewTunnelSessionDescription(
+ CN_TUNNEL, new TunnelContentDescription(description));
+}
+
+SessionDescription* TunnelSessionClient::CreateAnswer(
+ const SessionDescription* offer) {
+ std::string content_name;
+ const TunnelContentDescription* offer_tunnel = NULL;
+ if (!FindTunnelContent(offer, &content_name, &offer_tunnel))
+ return NULL;
+
+ return NewTunnelSessionDescription(
+ content_name, new TunnelContentDescription(offer_tunnel->description));
+}
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+///////////////////////////////////////////////////////////////////////////////
+
+//
+// Signalling thread methods
+//
+
+TunnelSession::TunnelSession(TunnelSessionClientBase* client, Session* session,
+ talk_base::Thread* stream_thread)
+ : client_(client), session_(session), channel_(NULL) {
+ ASSERT(client_ != NULL);
+ ASSERT(session_ != NULL);
+ session_->SignalState.connect(this, &TunnelSession::OnSessionState);
+ channel_ = new PseudoTcpChannel(stream_thread, session_);
+ channel_->SignalChannelClosed.connect(this, &TunnelSession::OnChannelClosed);
+}
+
+TunnelSession::~TunnelSession() {
+ ASSERT(client_ != NULL);
+ ASSERT(session_ == NULL);
+ ASSERT(channel_ == NULL);
+}
+
+talk_base::StreamInterface* TunnelSession::GetStream() {
+ ASSERT(channel_ != NULL);
+ return channel_->GetStream();
+}
+
+bool TunnelSession::HasSession(Session* session) {
+ ASSERT(NULL != session_);
+ return (session_ == session);
+}
+
+Session* TunnelSession::ReleaseSession(bool channel_exists) {
+ ASSERT(NULL != session_);
+ ASSERT(NULL != channel_);
+ Session* session = session_;
+ session_->SignalState.disconnect(this);
+ session_ = NULL;
+ if (channel_exists)
+ channel_->SignalChannelClosed.disconnect(this);
+ channel_ = NULL;
+ delete this;
+ return session;
+}
+
+void TunnelSession::OnSessionState(BaseSession* session,
+ BaseSession::State state) {
+ LOG(LS_INFO) << "TunnelSession::OnSessionState("
+ << talk_base::nonnull(
+ talk_base::FindLabel(state, SESSION_STATES), "Unknown")
+ << ")";
+ ASSERT(session == session_);
+
+ switch (state) {
+ case Session::STATE_RECEIVEDINITIATE:
+ OnInitiate();
+ break;
+ case Session::STATE_SENTACCEPT:
+ case Session::STATE_RECEIVEDACCEPT:
+ OnAccept();
+ break;
+ case Session::STATE_SENTTERMINATE:
+ case Session::STATE_RECEIVEDTERMINATE:
+ OnTerminate();
+ break;
+ case Session::STATE_DEINIT:
+ // ReleaseSession should have been called before this.
+ ASSERT(false);
+ break;
+ default:
+ break;
+ }
+}
+
+void TunnelSession::OnInitiate() {
+ ASSERT(client_ != NULL);
+ ASSERT(session_ != NULL);
+ client_->OnIncomingTunnel(buzz::Jid(session_->remote_name()), session_);
+}
+
+void TunnelSession::OnAccept() {
+ ASSERT(channel_ != NULL);
+ const ContentInfo* content =
+ session_->remote_description()->FirstContentByType(NS_TUNNEL);
+ ASSERT(content != NULL);
+ VERIFY(channel_->Connect(content->name, "tcp"));
+}
+
+void TunnelSession::OnTerminate() {
+ ASSERT(channel_ != NULL);
+ channel_->OnSessionTerminate(session_);
+}
+
+void TunnelSession::OnChannelClosed(PseudoTcpChannel* channel) {
+ ASSERT(channel_ == channel);
+ ASSERT(session_ != NULL);
+ session_->Terminate();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
diff --git a/talk/session/tunnel/tunnelsessionclient.h b/talk/session/tunnel/tunnelsessionclient.h
new file mode 100644
index 0000000..7259fa1
--- /dev/null
+++ b/talk/session/tunnel/tunnelsessionclient.h
@@ -0,0 +1,182 @@
+/*
+ * libjingle
+ * Copyright 2004--2008, Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+ * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __TUNNELSESSIONCLIENT_H__
+#define __TUNNELSESSIONCLIENT_H__
+
+#include <vector>
+
+#include "talk/base/criticalsection.h"
+#include "talk/base/stream.h"
+#include "talk/p2p/base/constants.h"
+#include "talk/p2p/base/pseudotcp.h"
+#include "talk/p2p/base/session.h"
+#include "talk/p2p/base/sessiondescription.h"
+#include "talk/p2p/base/sessionmanager.h"
+#include "talk/p2p/base/sessionclient.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/constants.h"
+
+namespace cricket {
+
+class TunnelSession;
+class TunnelStream;
+
+enum TunnelSessionRole { INITIATOR, RESPONDER };
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSessionClient
+///////////////////////////////////////////////////////////////////////////////
+
+// Base class is still abstract
+class TunnelSessionClientBase
+ : public SessionClient, public talk_base::MessageHandler {
+public:
+ TunnelSessionClientBase(const buzz::Jid& jid, SessionManager* manager,
+ const std::string &ns);
+ virtual ~TunnelSessionClientBase();
+
+ const buzz::Jid& jid() const { return jid_; }
+ SessionManager* session_manager() const { return session_manager_; }
+
+ void OnSessionCreate(Session* session, bool received);
+ void OnSessionDestroy(Session* session);
+
+ // This can be called on any thread. The stream interface is
+ // thread-safe, but notifications must be registered on the creating
+ // thread.
+ talk_base::StreamInterface* CreateTunnel(const buzz::Jid& to,
+ const std::string& description);
+
+ talk_base::StreamInterface* AcceptTunnel(Session* session);
+ void DeclineTunnel(Session* session);
+
+ // Invoked on an incoming tunnel
+ virtual void OnIncomingTunnel(const buzz::Jid &jid, Session *session) = 0;
+
+ // Invoked on an outgoing session request
+ virtual SessionDescription* CreateOffer(
+ const buzz::Jid &jid, const std::string &description) = 0;
+ // Invoked on a session request accept to create
+ // the local-side session description
+ virtual SessionDescription* CreateAnswer(
+ const SessionDescription* offer) = 0;
+
+protected:
+
+ void OnMessage(talk_base::Message* pmsg);
+
+ // helper method to instantiate TunnelSession. By overriding this,
+ // subclasses of TunnelSessionClient are able to instantiate
+ // subclasses of TunnelSession instead.
+ virtual TunnelSession* MakeTunnelSession(Session* session,
+ talk_base::Thread* stream_thread,
+ TunnelSessionRole role);
+
+ buzz::Jid jid_;
+ SessionManager* session_manager_;
+ std::vector<TunnelSession*> sessions_;
+ std::string namespace_;
+ bool shutdown_;
+};
+
+class TunnelSessionClient
+ : public TunnelSessionClientBase, public sigslot::has_slots<> {
+public:
+ TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager);
+ TunnelSessionClient(const buzz::Jid& jid, SessionManager* manager,
+ const std::string &ns);
+ virtual ~TunnelSessionClient();
+
+ virtual bool ParseContent(SignalingProtocol protocol,
+ const buzz::XmlElement* elem,
+ const ContentDescription** content,
+ ParseError* error);
+ virtual bool WriteContent(SignalingProtocol protocol,
+ const ContentDescription* content,
+ buzz::XmlElement** elem,
+ WriteError* error);
+
+ // Signal arguments are this, initiator, description, session
+ sigslot::signal4<TunnelSessionClient*, buzz::Jid, std::string, Session*>
+ SignalIncomingTunnel;
+
+ virtual void OnIncomingTunnel(const buzz::Jid &jid,
+ Session *session);
+ virtual SessionDescription* CreateOffer(
+ const buzz::Jid &jid, const std::string &description);
+ virtual SessionDescription* CreateAnswer(
+ const SessionDescription* offer);
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// TunnelSession
+// Note: The lifetime of TunnelSession is complicated. It needs to survive
+// until the following three conditions are true:
+// 1) TunnelStream has called Close (tracked via non-null stream_)
+// 2) PseudoTcp has completed (tracked via non-null tcp_)
+// 3) Session has been destroyed (tracked via non-null session_)
+// This is accomplished by calling CheckDestroy after these indicators change.
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+// TunnelStream
+// Note: Because TunnelStream provides a stream interface, its lifetime is
+// controlled by the owner of the stream pointer. As a result, we must support
+// both the TunnelSession disappearing before TunnelStream, and vice versa.
+///////////////////////////////////////////////////////////////////////////////
+
+class PseudoTcpChannel;
+
+class TunnelSession : public sigslot::has_slots<> {
+ public:
+ // Signalling thread methods
+ TunnelSession(TunnelSessionClientBase* client, Session* session,
+ talk_base::Thread* stream_thread);
+
+ virtual talk_base::StreamInterface* GetStream();
+ bool HasSession(Session* session);
+ Session* ReleaseSession(bool channel_exists);
+
+ protected:
+ virtual ~TunnelSession();
+
+ virtual void OnSessionState(BaseSession* session, BaseSession::State state);
+ virtual void OnInitiate();
+ virtual void OnAccept();
+ virtual void OnTerminate();
+ virtual void OnChannelClosed(PseudoTcpChannel* channel);
+
+ TunnelSessionClientBase* client_;
+ Session* session_;
+ PseudoTcpChannel* channel_;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+} // namespace cricket
+
+#endif // __TUNNELSESSIONCLIENT_H__
diff --git a/talk/site_scons/site_tools/talk_noops.py b/talk/site_scons/site_tools/talk_noops.py
new file mode 100644
index 0000000..bb8f106
--- /dev/null
+++ b/talk/site_scons/site_tools/talk_noops.py
@@ -0,0 +1,20 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+# Author: thaloun@google.com (Tim Haloun)
+
+"""Noop tool that defines builder functions for non-default platforms to
+ avoid errors when scanning sconsscripts."""
+
+import SCons.Builder
+
+
+def generate(env):
+ """SCons method."""
+ if not env.Bit('windows'):
+ builder = SCons.Builder.Builder(
+ action=''
+ )
+ env.Append(BUILDERS={'RES': builder, 'Grit': builder})
+
+def exists(env):
+ return 1
diff --git a/talk/site_scons/talk.py b/talk/site_scons/talk.py
new file mode 100644
index 0000000..09e4dd9
--- /dev/null
+++ b/talk/site_scons/talk.py
@@ -0,0 +1,561 @@
+# Copyright 2010 Google Inc.
+# All Rights Reserved.
+#
+# Author: Tim Haloun (thaloun@google.com)
+# Daniel Petersson (dape@google.com)
+#
+import os
+
+# Keep a global dictionary of library target params for lookups in
+# ExtendComponent().
+_all_lib_targets = {}
+
+def _GenericLibrary(env, static, **kwargs):
+ """Extends ComponentLibrary to support multiplatform builds
+ of dynamic or static libraries.
+
+ Args:
+ env: The environment object.
+ kwargs: The keyword arguments.
+
+ Returns:
+ See swtoolkit ComponentLibrary
+ """
+ params = CombineDicts(kwargs, {'COMPONENT_STATIC': static})
+ return ExtendComponent(env, 'ComponentLibrary', **params)
+
+
+def Library(env, **kwargs):
+ """Extends ComponentLibrary to support multiplatform builds of static
+ libraries.
+
+ Args:
+ env: The current environment.
+ kwargs: The keyword arguments.
+
+ Returns:
+ See swtoolkit ComponentLibrary
+ """
+ return _GenericLibrary(env, True, **kwargs)
+
+
+def DynamicLibrary(env, **kwargs):
+ """Extends ComponentLibrary to support multiplatform builds
+ of dynmic libraries.
+
+ Args:
+ env: The environment object.
+ kwargs: The keyword arguments.
+
+ Returns:
+ See swtoolkit ComponentLibrary
+ """
+ return _GenericLibrary(env, False, **kwargs)
+
+
+def Object(env, **kwargs):
+ return ExtendComponent(env, 'ComponentObject', **kwargs)
+
+
+def Unittest(env, **kwargs):
+ """Extends ComponentTestProgram to support unittest built
+ for multiple platforms.
+
+ Args:
+ env: The current environment.
+ kwargs: The keyword arguments.
+
+ Returns:
+ See swtoolkit ComponentProgram.
+ """
+ kwargs['name'] = kwargs['name'] + '_unittest'
+
+ common_test_params = {
+ 'posix_cppdefines': ['GUNIT_NO_GOOGLE3', 'GTEST_HAS_RTTI=0'],
+ 'libs': ['unittest_main', 'gunit']
+ }
+ if not kwargs.has_key('explicit_libs'):
+ common_test_params['win_libs'] = [
+ 'advapi32',
+ 'crypt32',
+ 'iphlpapi',
+ 'secur32',
+ 'shell32',
+ 'shlwapi',
+ 'user32',
+ 'wininet',
+ 'ws2_32'
+ ]
+ common_test_params['lin_libs'] = [
+ 'crypto',
+ 'pthread',
+ 'ssl',
+ ]
+
+ params = CombineDicts(kwargs, common_test_params)
+ return ExtendComponent(env, 'ComponentTestProgram', **params)
+
+
+def App(env, **kwargs):
+ """Extends ComponentProgram to support executables with platform specific
+ options.
+
+ Args:
+ env: The current environment.
+ kwargs: The keyword arguments.
+
+ Returns:
+ See swtoolkit ComponentProgram.
+ """
+ if not kwargs.has_key('explicit_libs'):
+ common_app_params = {
+ 'win_libs': [
+ 'advapi32',
+ 'crypt32',
+ 'iphlpapi',
+ 'secur32',
+ 'shell32',
+ 'shlwapi',
+ 'user32',
+ 'wininet',
+ 'ws2_32'
+ ]}
+ params = CombineDicts(kwargs, common_app_params)
+ else:
+ params = kwargs
+ return ExtendComponent(env, 'ComponentProgram', **params)
+
+def WiX(env, **kwargs):
+ """ Extends the WiX builder
+ Args:
+ env: The current environment.
+ kwargs: The keyword arguments.
+
+ Returns:
+ The node produced by the environment's wix builder
+ """
+ return ExtendComponent(env, 'WiX', **kwargs)
+
+def Repository(env, at, path):
+ """Maps a directory external to $MAIN_DIR to the given path so that sources
+ compiled from it end up in the correct place under $OBJ_DIR. NOT required
+ when only referring to header files.
+
+ Args:
+ env: The current environment object.
+ at: The 'mount point' within the current directory.
+ path: Path to the actual directory.
+ """
+ env.Dir(at).addRepository(env.Dir(path))
+
+
+def Components(*paths):
+ """Completes the directory paths with the correct file
+ names such that the directory/directory.scons name
+ convention can be used.
+
+ Args:
+ paths: The paths to complete. If it refers to an existing
+ file then it is ignored.
+
+ Returns:
+ The completed lif scons files that are needed to build talk.
+ """
+ files = []
+ for path in paths:
+ if os.path.isfile(path):
+ files.append(path)
+ else:
+ files.append(ExpandSconsPath(path))
+ return files
+
+
+def ExpandSconsPath(path):
+ """Expands a directory path into the path to the
+ scons file that our build uses.
+ Ex: magiflute/plugin/common => magicflute/plugin/common/common.scons
+
+ Args:
+ path: The directory path to expand.
+
+ Returns:
+ The expanded path.
+ """
+ return '%s/%s.scons' % (path, os.path.basename(path))
+
+
+def AddMediaLibs(env, **kwargs):
+ lmi_libdir = '$GOOGLE3/../googleclient/third_party/lmi/files/lib/'
+ if env.Bit('windows'):
+ if env.get('COVERAGE_ENABLED'):
+ lmi_libdir += 'win32/c_only'
+ else:
+ lmi_libdir += 'win32/Release'
+ elif env.Bit('mac'):
+ lmi_libdir += 'macos'
+ elif env.Bit('linux'):
+ lmi_libdir += 'linux/x86'
+
+
+ AddToDict(kwargs, 'libdirs', [
+ '$MAIN_DIR/third_party/gips/Libraries/',
+ lmi_libdir,
+ ])
+
+ gips_lib = ''
+ if env.Bit('windows'):
+ if env.Bit('debug'):
+ gips_lib = 'gipsvoiceenginelib_mtd'
+ else:
+ gips_lib = 'gipsvoiceenginelib_mt'
+ elif env.Bit('mac'):
+ gips_lib = 'VoiceEngine_mac_universal_gcc'
+ elif env.Bit('linux'):
+ gips_lib = 'VoiceEngine_Linux_gcc'
+
+
+ AddToDict(kwargs, 'libs', [
+ gips_lib,
+ 'LmiAudioCommon',
+ 'LmiClient',
+ 'LmiCmcp',
+ 'LmiDeviceManager',
+ 'LmiH263ClientPlugIn',
+ 'LmiH263CodecCommon',
+ 'LmiH263Decoder',
+ 'LmiH263Encoder',
+ 'LmiH264ClientPlugIn',
+ 'LmiH264CodecCommon',
+ 'LmiH264Common',
+ 'LmiH264Decoder',
+ 'LmiH264Encoder',
+ 'LmiIce',
+ 'LmiMediaPayload',
+ 'LmiOs',
+ 'LmiPacketCache',
+ 'LmiProtocolStack',
+ 'LmiRateShaper',
+ 'LmiRtp',
+ 'LmiSecurity',
+ 'LmiSignaling',
+ 'LmiStun',
+ 'LmiTransport',
+ 'LmiUi',
+ 'LmiUtils',
+ 'LmiVideoCommon',
+ 'LmiXml',
+ ])
+
+ if env.Bit('windows'):
+ AddToDict(kwargs, 'libs', [
+ 'dsound',
+ 'd3d9',
+ 'gdi32',
+ 'strmiids',
+ ])
+
+ if env.Bit('mac'):
+ AddToDict(kwargs, 'FRAMEWORKS', [
+ 'AudioToolbox',
+ 'AudioUnit',
+ 'Cocoa',
+ 'CoreAudio',
+ 'CoreFoundation',
+ 'IOKit',
+ 'QTKit',
+ 'QuickTime',
+ 'QuartzCore',
+ ])
+ return kwargs
+
+
+def ReadVersion(filename):
+ """Executes the supplied file and pulls out a version definition from it. """
+ defs = {}
+ execfile(str(filename), defs)
+ if not defs.has_key('version'):
+ return '0.0.0.0'
+ version = defs['version']
+ parts = version.split(',')
+ build = os.environ.get('GOOGLE_VERSION_BUILDNUMBER')
+ if build:
+ parts[-1] = str(build)
+ return '.'.join(parts)
+
+
+#-------------------------------------------------------------------------------
+# Helper methods for translating talk.Foo() declarations in to manipulations of
+# environmuent construction variables, including parameter parsing and merging,
+#
+def GetEntry(dict, key):
+ """Get the value from a dictionary by key. If the key
+ isn't in the dictionary then None is returned. If it is in
+ the dictionaruy the value is fetched and then is it removed
+ from the dictionary.
+
+ Args:
+ key: The key to get the value for.
+ kwargs: The keyword argument dictionary.
+ Returns:
+ The value or None if the key is missing.
+ """
+ value = None
+ if dict.has_key(key):
+ value = dict[key]
+ dict.pop(key)
+
+ return value
+
+
+def MergeAndFilterByPlatform(env, params):
+ """Take a dictionary of arguments to lists of values, and, depending on
+ which platform we are targetting, merge the lists of associated keys.
+ Merge by combining value lists like so:
+ {win_foo = [a,b], lin_foo = [c,d], foo = [e], mac_bar = [f], bar = [g] }
+ becomes {foo = [a,b,e], bar = [g]} on windows, and
+ {foo = [e], bar = [f,g]} on mac
+
+ Args:
+ env: The hammer environment which knows which platforms are active
+ params: The keyword argument dictionary.
+ Returns:
+ A new dictionary with the filtered and combined entries of params
+ """
+ platforms = {
+ 'linux': 'lin_',
+ 'mac': 'mac_',
+ 'posix': 'posix_',
+ 'windows': 'win_',
+ }
+ active_prefixes = [
+ platforms[x] for x in iter(platforms) if env.Bit(x)
+ ]
+ inactive_prefixes = [
+ platforms[x] for x in iter(platforms) if not env.Bit(x)
+ ]
+
+ merged = {}
+ for arg, values in params.iteritems():
+ inactive_platform = False
+
+ key = arg
+
+ for prefix in active_prefixes:
+ if arg.startswith(prefix):
+ key = arg[len(prefix):]
+
+ for prefix in inactive_prefixes:
+ if arg.startswith(prefix):
+ inactive_platform = True
+
+ if inactive_platform:
+ continue
+
+ AddToDict(merged, key, values)
+
+ return merged
+
+# Linux can build both 32 and 64 bit on 64 bit host, but 32 bit host can
+# only build 32 bit. For 32 bit debian installer a 32 bit host is required.
+# ChromeOS (linux) ebuild don't support 64 bit and requires 32 bit build only
+# for now.
+def Allow64BitCompile(env):
+ return (env.Bit('linux') and env.Bit('platform_arch_64bit')
+ )
+
+def MergeSettingsFromLibraryDependencies(env, params):
+ if params.has_key('libs'):
+ for lib in params['libs']:
+ if (_all_lib_targets.has_key(lib) and
+ _all_lib_targets[lib].has_key('dependent_target_settings')):
+ params = CombineDicts(
+ params,
+ MergeAndFilterByPlatform(
+ env,
+ _all_lib_targets[lib]['dependent_target_settings']))
+ return params
+
+def ExtendComponent(env, component, **kwargs):
+ """A wrapper around a scons builder function that preprocesses and post-
+ processes its inputs and outputs. For example, it merges and filters
+ certain keyword arguments before appending them to the environments
+ construction variables. It can build signed targets and 64bit copies
+ of targets as well.
+
+ Args:
+ env: The hammer environment with which to build the target
+ component: The environment's builder function, e.g. ComponentProgram
+ kwargs: keyword arguments that are either merged, translated, and passed on
+ to the call to component, or which control execution.
+ TODO(): Document the fields, such as cppdefines->CPPDEFINES,
+ prepend_includedirs, include_talk_media_libs, etc.
+ Returns:
+ The output node returned by the call to component, or a subsequent signed
+ dependant node.
+ """
+ env = env.Clone()
+
+ # prune parameters intended for other platforms, then merge
+ params = MergeAndFilterByPlatform(env, kwargs)
+
+ # get the 'target' field
+ name = GetEntry(params, 'name')
+
+ # save pristine params of lib targets for future reference
+ if 'ComponentLibrary' == component:
+ _all_lib_targets[name] = dict(params)
+
+ # add any dependent target settings from library dependencies
+ params = MergeSettingsFromLibraryDependencies(env, params)
+
+ # if this is a signed binary we need to make an unsigned version first
+ signed = env.Bit('windows') and GetEntry(params, 'signed')
+ if signed:
+ name = 'unsigned_' + name
+
+ # add default values
+ if GetEntry(params, 'include_talk_media_libs'):
+ params = AddMediaLibs(env, **params)
+
+ # potentially exit now
+ srcs = GetEntry(params, 'srcs')
+ if not srcs or not hasattr(env, component):
+ return None
+
+ # apply any explicit dependencies
+ dependencies = GetEntry(params, 'depends')
+ if dependencies is not None:
+ env.Depends(name, dependencies)
+
+ # put the contents of params into the environment
+ # some entries are renamed then appended, others renamed then prepended
+ appends = {
+ 'cppdefines' : 'CPPDEFINES',
+ 'libdirs' : 'LIBPATH',
+ 'link_flags' : 'LINKFLAGS',
+ 'libs' : 'LIBS',
+ 'FRAMEWORKS' : 'FRAMEWORKS',
+ }
+ prepends = {}
+ if env.Bit('windows'):
+ # MSVC compile flags have precedence at the beginning ...
+ prepends['ccflags'] = 'CCFLAGS'
+ else:
+ # ... while GCC compile flags have precedence at the end
+ appends['ccflags'] = 'CCFLAGS'
+ if GetEntry(params, 'prepend_includedirs'):
+ prepends['includedirs'] = 'CPPPATH'
+ else:
+ appends['includedirs'] = 'CPPPATH'
+
+ for field, var in appends.items():
+ values = GetEntry(params, field)
+ if values is not None:
+ env.Append(**{var : values})
+ for field, var in prepends.items():
+ values = GetEntry(params, field)
+ if values is not None:
+ env.Prepend(**{var : values})
+
+ # workaround for pulse stripping link flag for unknown reason
+ if Allow64BitCompile(env):
+ env['SHLINKCOM'] = ('$SHLINK -o $TARGET -m32 $SHLINKFLAGS $SOURCES '
+ '$_LIBDIRFLAGS $_LIBFLAGS')
+ env['LINKCOM'] = ('$LINK -o $TARGET -m32 $LINKFLAGS $SOURCES '
+ '$_LIBDIRFLAGS $_LIBFLAGS')
+
+ # any other parameters are replaced without renaming
+ for field, value in params.items():
+ env.Replace(**{field : value})
+
+ # invoke the builder function
+ builder = getattr(env, component)
+
+ node = builder(name, srcs)
+
+ # make a parallel 64bit version if requested
+ if Allow64BitCompile(env) and GetEntry(params, 'also64bit'):
+ env_64bit = env.Clone()
+ env_64bit.FilterOut(CCFLAGS = ['-m32'], LINKFLAGS = ['-m32'])
+ env_64bit.Prepend(CCFLAGS = ['-m64', '-fPIC'], LINKFLAGS = ['-m64'])
+ name_64bit = name + '64'
+ env_64bit.Replace(OBJSUFFIX = '64' + env_64bit['OBJSUFFIX'])
+ env_64bit.Replace(SHOBJSUFFIX = '64' + env_64bit['SHOBJSUFFIX'])
+ if ('ComponentProgram' == component or
+ ('ComponentLibrary' == component and
+ env_64bit['COMPONENT_STATIC'] == False)):
+ # link 64 bit versions of libraries
+ libs = []
+ for lib in env_64bit['LIBS']:
+ if (_all_lib_targets.has_key(lib) and
+ _all_lib_targets[lib].has_key('also64bit')):
+ libs.append(lib + '64')
+ else:
+ libs.append(lib)
+ env_64bit.Replace(LIBS = libs)
+
+ env_64bit['SHLINKCOM'] = ('$SHLINK -o $TARGET -m64 $SHLINKFLAGS $SOURCES '
+ '$_LIBDIRFLAGS $_LIBFLAGS')
+ env_64bit['LINKCOM'] = ('$LINK -o $TARGET -m64 $LINKFLAGS $SOURCES '
+ '$_LIBDIRFLAGS $_LIBFLAGS')
+ builder = getattr(env_64bit, component)
+ nodes = [node, builder(name_64bit, srcs)]
+ return nodes
+
+ if signed: # Note currently incompatible with 64Bit flag
+ # Get the name of the built binary, then get the name of the final signed
+ # version from it. We need the output path since we don't know the file
+ # extension beforehand.
+ target = node[0].path.split('_', 1)[1]
+ signed_node = env.SignedBinary(
+ source = node,
+ target = '$STAGING_DIR/' + target,
+ )
+ env.Alias('signed_binaries', signed_node)
+ return signed_node
+
+ return node
+
+
+def AddToDict(dictionary, key, values, append=True):
+ """Merge the given key value(s) pair into a dictionary. If it contains an
+ entry with that key already, then combine by appending or prepending the
+ values as directed. Otherwise, assign a new keyvalue pair.
+ """
+ if values is None:
+ return
+
+ if not dictionary.has_key(key):
+ dictionary[key] = values
+ return
+
+ cur = dictionary[key]
+ # TODO: Make sure that there are no duplicates
+ # in the list. I can't use python set for this since
+ # the nodes that are returned by the SCONS builders
+ # are not hashable.
+ # dictionary[key] = list(set(cur).union(set(values)))
+ if append:
+ dictionary[key] = cur + values
+ else:
+ dictionary[key] = values + cur
+
+
+def CombineDicts(a, b):
+ """Unions two dictionaries by combining values of keys shared between them.
+ """
+ c = {}
+ for key in a:
+ if b.has_key(key):
+ c[key] = a[key] + b.pop(key)
+ else:
+ c[key] = a[key]
+
+ for key in b:
+ c[key] = b[key]
+
+ return c
+
+
+def RenameKey(d, old, new, append=True):
+ AddToDict(d, new, GetEntry(d, old), append)
diff --git a/talk/third_party/libudev/libudev.h b/talk/third_party/libudev/libudev.h
new file mode 100644
index 0000000..5bc42df
--- /dev/null
+++ b/talk/third_party/libudev/libudev.h
@@ -0,0 +1,175 @@
+/*
+ * libudev - interface to udev device information
+ *
+ * Copyright (C) 2008-2010 Kay Sievers <kay.sievers@vrfy.org>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ */
+
+#ifndef _LIBUDEV_H_
+#define _LIBUDEV_H_
+
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * udev - library context
+ *
+ * reads the udev config and system environment
+ * allows custom logging
+ */
+struct udev;
+struct udev *udev_ref(struct udev *udev);
+void udev_unref(struct udev *udev);
+struct udev *udev_new(void);
+void udev_set_log_fn(struct udev *udev,
+ void (*log_fn)(struct udev *udev,
+ int priority, const char *file, int line, const char *fn,
+ const char *format, va_list args));
+int udev_get_log_priority(struct udev *udev);
+void udev_set_log_priority(struct udev *udev, int priority);
+const char *udev_get_sys_path(struct udev *udev);
+const char *udev_get_dev_path(struct udev *udev);
+void *udev_get_userdata(struct udev *udev);
+void udev_set_userdata(struct udev *udev, void *userdata);
+
+/*
+ * udev_list
+ *
+ * access to libudev generated lists
+ */
+struct udev_list_entry;
+struct udev_list_entry *udev_list_entry_get_next(struct udev_list_entry *list_entry);
+struct udev_list_entry *udev_list_entry_get_by_name(struct udev_list_entry *list_entry, const char *name);
+const char *udev_list_entry_get_name(struct udev_list_entry *list_entry);
+const char *udev_list_entry_get_value(struct udev_list_entry *list_entry);
+/**
+ * udev_list_entry_foreach:
+ * @list_entry: entry to store the current position
+ * @first_entry: first entry to start with
+ *
+ * Helper to iterate over all entries of a list.
+ */
+#define udev_list_entry_foreach(list_entry, first_entry) \
+ for (list_entry = first_entry; \
+ list_entry != NULL; \
+ list_entry = udev_list_entry_get_next(list_entry))
+
+/*
+ * udev_device
+ *
+ * access to sysfs/kernel devices
+ */
+struct udev_device;
+struct udev_device *udev_device_ref(struct udev_device *udev_device);
+void udev_device_unref(struct udev_device *udev_device);
+struct udev *udev_device_get_udev(struct udev_device *udev_device);
+struct udev_device *udev_device_new_from_syspath(struct udev *udev, const char *syspath);
+struct udev_device *udev_device_new_from_devnum(struct udev *udev, char type, dev_t devnum);
+struct udev_device *udev_device_new_from_subsystem_sysname(struct udev *udev, const char *subsystem, const char *sysname);
+struct udev_device *udev_device_new_from_environment(struct udev *udev);
+/* udev_device_get_parent_*() does not take a reference on the returned device, it is automatically unref'd with the parent */
+struct udev_device *udev_device_get_parent(struct udev_device *udev_device);
+struct udev_device *udev_device_get_parent_with_subsystem_devtype(struct udev_device *udev_device,
+ const char *subsystem, const char *devtype);
+/* retrieve device properties */
+const char *udev_device_get_devpath(struct udev_device *udev_device);
+const char *udev_device_get_subsystem(struct udev_device *udev_device);
+const char *udev_device_get_devtype(struct udev_device *udev_device);
+const char *udev_device_get_syspath(struct udev_device *udev_device);
+const char *udev_device_get_sysname(struct udev_device *udev_device);
+const char *udev_device_get_sysnum(struct udev_device *udev_device);
+const char *udev_device_get_devnode(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_devlinks_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_properties_list_entry(struct udev_device *udev_device);
+struct udev_list_entry *udev_device_get_tags_list_entry(struct udev_device *udev_device);
+const char *udev_device_get_property_value(struct udev_device *udev_device, const char *key);
+const char *udev_device_get_driver(struct udev_device *udev_device);
+dev_t udev_device_get_devnum(struct udev_device *udev_device);
+const char *udev_device_get_action(struct udev_device *udev_device);
+unsigned long long int udev_device_get_seqnum(struct udev_device *udev_device);
+const char *udev_device_get_sysattr_value(struct udev_device *udev_device, const char *sysattr);
+
+/*
+ * udev_monitor
+ *
+ * access to kernel uevents and udev events
+ */
+struct udev_monitor;
+struct udev_monitor *udev_monitor_ref(struct udev_monitor *udev_monitor);
+void udev_monitor_unref(struct udev_monitor *udev_monitor);
+struct udev *udev_monitor_get_udev(struct udev_monitor *udev_monitor);
+/* kernel and udev generated events over netlink */
+struct udev_monitor *udev_monitor_new_from_netlink(struct udev *udev, const char *name);
+/* custom socket (use netlink and filters instead) */
+struct udev_monitor *udev_monitor_new_from_socket(struct udev *udev, const char *socket_path);
+/* bind socket */
+int udev_monitor_enable_receiving(struct udev_monitor *udev_monitor);
+int udev_monitor_set_receive_buffer_size(struct udev_monitor *udev_monitor, int size);
+int udev_monitor_get_fd(struct udev_monitor *udev_monitor);
+struct udev_device *udev_monitor_receive_device(struct udev_monitor *udev_monitor);
+/* in-kernel socket filters to select messages that get delivered to a listener */
+int udev_monitor_filter_add_match_subsystem_devtype(struct udev_monitor *udev_monitor,
+ const char *subsystem, const char *devtype);
+int udev_monitor_filter_add_match_tag(struct udev_monitor *udev_monitor, const char *tag);
+int udev_monitor_filter_update(struct udev_monitor *udev_monitor);
+int udev_monitor_filter_remove(struct udev_monitor *udev_monitor);
+
+/*
+ * udev_enumerate
+ *
+ * search sysfs for specific devices and provide a sorted list
+ */
+struct udev_enumerate;
+struct udev_enumerate *udev_enumerate_ref(struct udev_enumerate *udev_enumerate);
+void udev_enumerate_unref(struct udev_enumerate *udev_enumerate);
+struct udev *udev_enumerate_get_udev(struct udev_enumerate *udev_enumerate);
+struct udev_enumerate *udev_enumerate_new(struct udev *udev);
+/* device properties filter */
+int udev_enumerate_add_match_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_nomatch_subsystem(struct udev_enumerate *udev_enumerate, const char *subsystem);
+int udev_enumerate_add_match_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_nomatch_sysattr(struct udev_enumerate *udev_enumerate, const char *sysattr, const char *value);
+int udev_enumerate_add_match_property(struct udev_enumerate *udev_enumerate, const char *property, const char *value);
+int udev_enumerate_add_match_sysname(struct udev_enumerate *udev_enumerate, const char *sysname);
+int udev_enumerate_add_match_tag(struct udev_enumerate *udev_enumerate, const char *tag);
+int udev_enumerate_add_syspath(struct udev_enumerate *udev_enumerate, const char *syspath);
+/* run enumeration with active filters */
+int udev_enumerate_scan_devices(struct udev_enumerate *udev_enumerate);
+int udev_enumerate_scan_subsystems(struct udev_enumerate *udev_enumerate);
+/* return device list */
+struct udev_list_entry *udev_enumerate_get_list_entry(struct udev_enumerate *udev_enumerate);
+
+/*
+ * udev_queue
+ *
+ * access to the currently running udev events
+ */
+struct udev_queue;
+struct udev_queue *udev_queue_ref(struct udev_queue *udev_queue);
+void udev_queue_unref(struct udev_queue *udev_queue);
+struct udev *udev_queue_get_udev(struct udev_queue *udev_queue);
+struct udev_queue *udev_queue_new(struct udev *udev);
+unsigned long long int udev_queue_get_kernel_seqnum(struct udev_queue *udev_queue);
+unsigned long long int udev_queue_get_udev_seqnum(struct udev_queue *udev_queue);
+int udev_queue_get_udev_is_active(struct udev_queue *udev_queue);
+int udev_queue_get_queue_is_empty(struct udev_queue *udev_queue);
+int udev_queue_get_seqnum_is_finished(struct udev_queue *udev_queue, unsigned long long int seqnum);
+int udev_queue_get_seqnum_sequence_is_finished(struct udev_queue *udev_queue,
+ unsigned long long int start, unsigned long long int end);
+struct udev_list_entry *udev_queue_get_queued_list_entry(struct udev_queue *udev_queue);
+struct udev_list_entry *udev_queue_get_failed_list_entry(struct udev_queue *udev_queue);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/talk/xmllite/qname.cc b/talk/xmllite/qname.cc
new file mode 100644
index 0000000..7486524
--- /dev/null
+++ b/talk/xmllite/qname.cc
@@ -0,0 +1,162 @@
+/*
+ * 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 <string>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+static int QName_Hash(const std::string & ns, const char * local) {
+ int result = static_cast<int>(ns.size()) * 101;
+ while (*local) {
+ result *= 19;
+ result += *local;
+ local += 1;
+ }
+ return result;
+}
+
+static const int bits = 9;
+static QName::Data * get_qname_table() {
+ static QName::Data qname_table[1 << bits];
+ return qname_table;
+}
+
+static QName::Data *
+AllocateOrFind(const std::string & ns, const char * local) {
+ int index = QName_Hash(ns, local);
+ int increment = index >> (bits - 1) | 1;
+ QName::Data * qname_table = get_qname_table();
+ for (;;) {
+ index &= ((1 << bits) - 1);
+ if (!qname_table[index].Occupied()) {
+ return new QName::Data(ns, local);
+ }
+ if (qname_table[index].localPart_ == local &&
+ qname_table[index].namespace_ == ns) {
+ qname_table[index].AddRef();
+ return qname_table + index;
+ }
+ index += increment;
+ }
+}
+
+static QName::Data *
+Add(const std::string & ns, const char * local) {
+ int index = QName_Hash(ns, local);
+ int increment = index >> (bits - 1) | 1;
+ QName::Data * qname_table = get_qname_table();
+ for (;;) {
+ index &= ((1 << bits) - 1);
+ if (!qname_table[index].Occupied()) {
+ qname_table[index].namespace_ = ns;
+ qname_table[index].localPart_ = local;
+ qname_table[index].AddRef(); // AddRef twice so it's never deleted
+ qname_table[index].AddRef();
+ return qname_table + index;
+ }
+ if (qname_table[index].localPart_ == local &&
+ qname_table[index].namespace_ == ns) {
+ qname_table[index].AddRef();
+ return qname_table + index;
+ }
+ index += increment;
+ }
+}
+
+QName::~QName() {
+ data_->Release();
+}
+
+QName::QName() : data_(QN_EMPTY.data_) {
+ data_->AddRef();
+}
+
+QName::QName(bool add, const std::string & ns, const char * local) :
+ data_(add ? Add(ns, local) : AllocateOrFind(ns, local)) {}
+
+QName::QName(bool add, const std::string & ns, const std::string & local) :
+ data_(add ? Add(ns, local.c_str()) : AllocateOrFind(ns, local.c_str())) {}
+
+QName::QName(const std::string & ns, const char * local) :
+ data_(AllocateOrFind(ns, local)) {}
+
+static std::string
+QName_LocalPart(const std::string & name) {
+ size_t i = name.rfind(':');
+ if (i == std::string::npos)
+ return name;
+ return name.substr(i + 1);
+}
+
+static std::string
+QName_Namespace(const std::string & name) {
+ size_t i = name.rfind(':');
+ if (i == std::string::npos)
+ return STR_EMPTY;
+ return name.substr(0, i);
+}
+
+QName::QName(const std::string & mergedOrLocal) :
+ data_(AllocateOrFind(QName_Namespace(mergedOrLocal),
+ QName_LocalPart(mergedOrLocal).c_str())) {}
+
+std::string
+QName::Merged() const {
+ if (data_->namespace_ == STR_EMPTY)
+ return data_->localPart_;
+
+ std::string result(data_->namespace_);
+ result.reserve(result.length() + 1 + data_->localPart_.length());
+ result += ':';
+ result += data_->localPart_;
+ return result;
+}
+
+bool
+QName::operator==(const QName & other) const {
+ return other.data_ == data_ ||
+ (data_->localPart_ == other.data_->localPart_ &&
+ data_->namespace_ == other.data_->namespace_);
+}
+
+int
+QName::Compare(const QName & other) const {
+ if (data_ == other.data_)
+ return 0;
+
+ int result = data_->localPart_.compare(other.data_->localPart_);
+ if (result)
+ return result;
+
+ return data_->namespace_.compare(other.data_->namespace_);
+}
+
+}
diff --git a/talk/xmllite/qname.h b/talk/xmllite/qname.h
new file mode 100644
index 0000000..3e64726
--- /dev/null
+++ b/talk/xmllite/qname.h
@@ -0,0 +1,87 @@
+/*
+ * 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 _qname_h_
+#define _qname_h_
+
+#include <string>
+
+namespace buzz {
+
+
+class QName
+{
+public:
+ explicit QName();
+ QName(const QName & qname) : data_(qname.data_) { data_->AddRef(); }
+ explicit QName(bool add, const std::string & ns, const char * local);
+ explicit QName(bool add, const std::string & ns, const std::string & local);
+ explicit QName(const std::string & ns, const char * local);
+ explicit QName(const std::string & mergedOrLocal);
+ QName & operator=(const QName & qn) {
+ qn.data_->AddRef();
+ data_->Release();
+ data_ = qn.data_;
+ return *this;
+ }
+ ~QName();
+
+ const std::string & Namespace() const { return data_->namespace_; }
+ const std::string & LocalPart() const { return data_->localPart_; }
+ std::string Merged() const;
+ int Compare(const QName & other) const;
+ bool operator==(const QName & other) const;
+ bool operator!=(const QName & other) const { return !operator==(other); }
+ bool operator<(const QName & other) const { return Compare(other) < 0; }
+
+ class Data {
+ public:
+ Data(const std::string & ns, const std::string & local) :
+ namespace_(ns),
+ localPart_(local),
+ refcount_(1) {}
+
+ Data() : refcount_(0) {}
+
+ std::string namespace_;
+ std::string localPart_;
+ void AddRef() { refcount_++; }
+ void Release() { if (!--refcount_) { delete this; } }
+ bool Occupied() { return !!refcount_; }
+
+ private:
+ int refcount_;
+ };
+
+private:
+ Data * data_;
+};
+
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlbuilder.cc b/talk/xmllite/xmlbuilder.cc
new file mode 100644
index 0000000..b02dfe0
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.cc
@@ -0,0 +1,152 @@
+/*
+ * 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 <vector>
+#include <set>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlbuilder.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+XmlBuilder::XmlBuilder() :
+ pelCurrent_(NULL),
+ pelRoot_(NULL),
+ pvParents_(new std::vector<XmlElement *>()) {
+}
+
+void
+XmlBuilder::Reset() {
+ pelRoot_.reset();
+ pelCurrent_ = NULL;
+ pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::BuildElement(XmlParseContext * pctx,
+ const char * name, const char ** atts) {
+ QName tagName(pctx->ResolveQName(name, false));
+ if (tagName == QN_EMPTY)
+ return NULL;
+
+ XmlElement * pelNew = new XmlElement(tagName);
+
+ if (!*atts)
+ return pelNew;
+
+ std::set<QName> seenNonlocalAtts;
+
+ while (*atts) {
+ QName attName(pctx->ResolveQName(*atts, true));
+ if (attName == QN_EMPTY) {
+ delete pelNew;
+ return NULL;
+ }
+
+ // verify that namespaced names are unique
+ if (!attName.Namespace().empty()) {
+ if (seenNonlocalAtts.count(attName)) {
+ delete pelNew;
+ return NULL;
+ }
+ seenNonlocalAtts.insert(attName);
+ }
+
+ pelNew->AddAttr(attName, std::string(*(atts + 1)));
+ atts += 2;
+ }
+
+ return pelNew;
+}
+
+void
+XmlBuilder::StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts) {
+ XmlElement * pelNew = BuildElement(pctx, name, atts);
+ if (pelNew == NULL) {
+ pctx->RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+
+ if (!pelCurrent_) {
+ pelCurrent_ = pelNew;
+ pelRoot_.reset(pelNew);
+ pvParents_->push_back(NULL);
+ } else {
+ pelCurrent_->AddElement(pelNew);
+ pvParents_->push_back(pelCurrent_);
+ pelCurrent_ = pelNew;
+ }
+}
+
+void
+XmlBuilder::EndElement(XmlParseContext * pctx, const char * name) {
+ UNUSED(pctx);
+ UNUSED(name);
+ pelCurrent_ = pvParents_->back();
+ pvParents_->pop_back();
+}
+
+void
+XmlBuilder::CharacterData(XmlParseContext * pctx,
+ const char * text, int len) {
+ UNUSED(pctx);
+ if (pelCurrent_) {
+ pelCurrent_->AddParsedText(text, len);
+ }
+}
+
+void
+XmlBuilder::Error(XmlParseContext * pctx, XML_Error err) {
+ UNUSED(pctx);
+ UNUSED(err);
+ pelRoot_.reset(NULL);
+ pelCurrent_ = NULL;
+ pvParents_->clear();
+}
+
+XmlElement *
+XmlBuilder::CreateElement() {
+ return pelRoot_.release();
+}
+
+XmlElement *
+XmlBuilder::BuiltElement() {
+ return pelRoot_.get();
+}
+
+XmlBuilder::~XmlBuilder() {
+}
+
+
+
+}
diff --git a/talk/xmllite/xmlbuilder.h b/talk/xmllite/xmlbuilder.h
new file mode 100644
index 0000000..d375985
--- /dev/null
+++ b/talk/xmllite/xmlbuilder.h
@@ -0,0 +1,78 @@
+/*
+ * 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 _xmlbuilder_h_
+#define _xmlbuilder_h_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/xmlparser.h"
+
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+class XmlElement;
+class XmlParseContext;
+
+
+class XmlBuilder : public XmlParseHandler {
+public:
+ XmlBuilder();
+
+ static XmlElement * BuildElement(XmlParseContext * pctx,
+ const char * name, const char ** atts);
+ virtual void StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts);
+ virtual void EndElement(XmlParseContext * pctx, const char * name);
+ virtual void CharacterData(XmlParseContext * pctx,
+ const char * text, int len);
+ virtual void Error(XmlParseContext * pctx, XML_Error);
+ virtual ~XmlBuilder();
+
+ void Reset();
+
+ // Take ownership of the built element; second call returns NULL
+ XmlElement * CreateElement();
+
+ // Peek at the built element without taking ownership
+ XmlElement * BuiltElement();
+
+private:
+ XmlElement * pelCurrent_;
+ talk_base::scoped_ptr<XmlElement> pelRoot_;
+ talk_base::scoped_ptr<std::vector<XmlElement*> > pvParents_;
+};
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlconstants.cc b/talk/xmllite/xmlconstants.cc
new file mode 100644
index 0000000..503f832
--- /dev/null
+++ b/talk/xmllite/xmlconstants.cc
@@ -0,0 +1,65 @@
+/*
+ * 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 "xmlconstants.h"
+
+using namespace buzz;
+
+const std::string & XmlConstants::str_empty() {
+ static const std::string str_empty_;
+ return str_empty_;
+}
+
+const std::string & XmlConstants::ns_xml() {
+ static const std::string ns_xml_("http://www.w3.org/XML/1998/namespace");
+ return ns_xml_;
+}
+
+const std::string & XmlConstants::ns_xmlns() {
+ static const std::string ns_xmlns_("http://www.w3.org/2000/xmlns/");
+ return ns_xmlns_;
+}
+
+const std::string & XmlConstants::str_xmlns() {
+ static const std::string str_xmlns_("xmlns");
+ return str_xmlns_;
+}
+
+const std::string & XmlConstants::str_xml() {
+ static const std::string str_xml_("xml");
+ return str_xml_;
+}
+
+const std::string & XmlConstants::str_version() {
+ static const std::string str_version_("version");
+ return str_version_;
+}
+
+const std::string & XmlConstants::str_encoding() {
+ static const std::string str_encoding_("encoding");
+ return str_encoding_;
+}
diff --git a/talk/xmllite/xmlconstants.h b/talk/xmllite/xmlconstants.h
new file mode 100644
index 0000000..8514d6f
--- /dev/null
+++ b/talk/xmllite/xmlconstants.h
@@ -0,0 +1,61 @@
+/*
+ * 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.
+ */
+
+// Because global constant initialization order is undefined
+// globals cannot depend on other objects to be instantiated.
+// This class creates string objects within static methods
+// such that globals may refer to these constants by the
+// accessor function and they are guaranteed to be initialized.
+
+#ifndef TALK_XMLLITE_CONSTANTS_H_
+#define TALK_XMLLITE_CONSTANTS_H_
+
+#include <string>
+
+#define STR_EMPTY XmlConstants::str_empty()
+#define NS_XML XmlConstants::ns_xml()
+#define NS_XMLNS XmlConstants::ns_xmlns()
+#define STR_XMLNS XmlConstants::str_xmlns()
+#define STR_XML XmlConstants::str_xml()
+#define STR_VERSION XmlConstants::str_version()
+#define STR_ENCODING XmlConstants::str_encoding()
+namespace buzz {
+
+class XmlConstants {
+ public:
+ static const std::string & str_empty();
+ static const std::string & ns_xml();
+ static const std::string & ns_xmlns();
+ static const std::string & str_xmlns();
+ static const std::string & str_xml();
+ static const std::string & str_version();
+ static const std::string & str_encoding();
+};
+
+}
+
+#endif // TALK_XMLLITE_CONSTANTS_H_
diff --git a/talk/xmllite/xmlelement.cc b/talk/xmllite/xmlelement.cc
new file mode 100644
index 0000000..3ec085c
--- /dev/null
+++ b/talk/xmllite/xmlelement.cc
@@ -0,0 +1,520 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+const QName QN_EMPTY(true, STR_EMPTY, STR_EMPTY);
+const QName QN_XMLNS(true, STR_EMPTY, STR_XMLNS);
+
+
+XmlChild::~XmlChild() {
+}
+
+bool
+XmlText::IsTextImpl() const {
+ return true;
+}
+
+XmlElement *
+XmlText::AsElementImpl() const {
+ return NULL;
+}
+
+XmlText *
+XmlText::AsTextImpl() const {
+ return const_cast<XmlText *>(this);
+}
+
+void
+XmlText::SetText(const std::string & text) {
+ text_ = text;
+}
+
+void
+XmlText::AddParsedText(const char * buf, int len) {
+ text_.append(buf, len);
+}
+
+void
+XmlText::AddText(const std::string & text) {
+ text_ += text;
+}
+
+XmlText::~XmlText() {
+}
+
+XmlElement::XmlElement(const QName & name) :
+ name_(name),
+ pFirstAttr_(NULL),
+ pLastAttr_(NULL),
+ pFirstChild_(NULL),
+ pLastChild_(NULL),
+ cdata_(false) {
+}
+
+XmlElement::XmlElement(const XmlElement & elt) :
+ XmlChild(),
+ name_(elt.name_),
+ pFirstAttr_(NULL),
+ pLastAttr_(NULL),
+ pFirstChild_(NULL),
+ pLastChild_(NULL),
+ cdata_(false) {
+
+ // copy attributes
+ XmlAttr * pAttr;
+ XmlAttr ** ppLastAttr = &pFirstAttr_;
+ XmlAttr * newAttr = NULL;
+ for (pAttr = elt.pFirstAttr_; pAttr; pAttr = pAttr->NextAttr()) {
+ newAttr = new XmlAttr(*pAttr);
+ *ppLastAttr = newAttr;
+ ppLastAttr = &(newAttr->pNextAttr_);
+ }
+ pLastAttr_ = newAttr;
+
+ // copy children
+ XmlChild * pChild;
+ XmlChild ** ppLast = &pFirstChild_;
+ XmlChild * newChild = NULL;
+
+ for (pChild = elt.pFirstChild_; pChild; pChild = pChild->NextChild()) {
+ if (pChild->IsText()) {
+ newChild = new XmlText(*(pChild->AsText()));
+ } else {
+ newChild = new XmlElement(*(pChild->AsElement()));
+ }
+ *ppLast = newChild;
+ ppLast = &(newChild->pNextChild_);
+ }
+ pLastChild_ = newChild;
+
+ cdata_ = elt.cdata_;
+}
+
+XmlElement::XmlElement(const QName & name, bool useDefaultNs) :
+ name_(name),
+ pFirstAttr_(useDefaultNs ? new XmlAttr(QN_XMLNS, name.Namespace()) : NULL),
+ pLastAttr_(pFirstAttr_),
+ pFirstChild_(NULL),
+ pLastChild_(NULL),
+ cdata_(false) {
+}
+
+bool
+XmlElement::IsTextImpl() const {
+ return false;
+}
+
+XmlElement *
+XmlElement::AsElementImpl() const {
+ return const_cast<XmlElement *>(this);
+}
+
+XmlText *
+XmlElement::AsTextImpl() const {
+ return NULL;
+}
+
+const std::string &
+XmlElement::BodyText() const {
+ if (pFirstChild_ && pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
+ return pFirstChild_->AsText()->Text();
+ }
+
+ return STR_EMPTY;
+}
+
+void
+XmlElement::SetBodyText(const std::string & text) {
+ if (text == STR_EMPTY) {
+ ClearChildren();
+ } else if (pFirstChild_ == NULL) {
+ AddText(text);
+ } else if (pFirstChild_->IsText() && pLastChild_ == pFirstChild_) {
+ pFirstChild_->AsText()->SetText(text);
+ } else {
+ ClearChildren();
+ AddText(text);
+ }
+}
+
+const QName &
+XmlElement::FirstElementName() const {
+ const XmlElement * element = FirstElement();
+ if (element == NULL)
+ return QN_EMPTY;
+ return element->Name();
+}
+
+XmlAttr *
+XmlElement::FirstAttr() {
+ return pFirstAttr_;
+}
+
+const std::string &
+XmlElement::Attr(const QName & name) const {
+ XmlAttr * pattr;
+ for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+ if (pattr->name_ == name)
+ return pattr->value_;
+ }
+ return STR_EMPTY;
+}
+
+bool
+XmlElement::HasAttr(const QName & name) const {
+ XmlAttr * pattr;
+ for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+ if (pattr->name_ == name)
+ return true;
+ }
+ return false;
+}
+
+void
+XmlElement::SetAttr(const QName & name, const std::string & value) {
+ XmlAttr * pattr;
+ for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+ if (pattr->name_ == name)
+ break;
+ }
+ if (!pattr) {
+ pattr = new XmlAttr(name, value);
+ if (pLastAttr_)
+ pLastAttr_->pNextAttr_ = pattr;
+ else
+ pFirstAttr_ = pattr;
+ pLastAttr_ = pattr;
+ return;
+ }
+ pattr->value_ = value;
+}
+
+void
+XmlElement::ClearAttr(const QName & name) {
+ XmlAttr * pattr;
+ XmlAttr *pLastAttr = NULL;
+ for (pattr = pFirstAttr_; pattr; pattr = pattr->pNextAttr_) {
+ if (pattr->name_ == name)
+ break;
+ pLastAttr = pattr;
+ }
+ if (!pattr)
+ return;
+ if (!pLastAttr)
+ pFirstAttr_ = pattr->pNextAttr_;
+ else
+ pLastAttr->pNextAttr_ = pattr->pNextAttr_;
+ if (pLastAttr_ == pattr)
+ pLastAttr_ = pLastAttr;
+ delete pattr;
+}
+
+XmlChild *
+XmlElement::FirstChild() {
+ return pFirstChild_;
+}
+
+XmlElement *
+XmlElement::FirstElement() {
+ XmlChild * pChild;
+ for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText())
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::NextElement() {
+ XmlChild * pChild;
+ for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText())
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::FirstWithNamespace(const std::string & ns) {
+ XmlChild * pChild;
+ for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::NextWithNamespace(const std::string & ns) {
+ XmlChild * pChild;
+ for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name().Namespace() == ns)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::FirstNamed(const QName & name) {
+ XmlChild * pChild;
+ for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement *
+XmlElement::NextNamed(const QName & name) {
+ XmlChild * pChild;
+ for (pChild = pNextChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+ return pChild->AsElement();
+ }
+ return NULL;
+}
+
+XmlElement* XmlElement::FindOrAddNamedChild(const QName& name) {
+ XmlElement* child = FirstNamed(name);
+ if (!child) {
+ child = new XmlElement(name);
+ AddElement(child);
+ }
+
+ return child;
+}
+
+const std::string &
+XmlElement::TextNamed(const QName & name) const {
+ XmlChild * pChild;
+ for (pChild = pFirstChild_; pChild; pChild = pChild->pNextChild_) {
+ if (!pChild->IsText() && pChild->AsElement()->Name() == name)
+ return pChild->AsElement()->BodyText();
+ }
+ return STR_EMPTY;
+}
+
+void
+XmlElement::InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNext) {
+ if (pPredecessor == NULL) {
+ pNext->pNextChild_ = pFirstChild_;
+ pFirstChild_ = pNext;
+ }
+ else {
+ pNext->pNextChild_ = pPredecessor->pNextChild_;
+ pPredecessor->pNextChild_ = pNext;
+ }
+}
+
+void
+XmlElement::RemoveChildAfter(XmlChild * pPredecessor) {
+ XmlChild * pNext;
+
+ if (pPredecessor == NULL) {
+ pNext = pFirstChild_;
+ pFirstChild_ = pNext->pNextChild_;
+ }
+ else {
+ pNext = pPredecessor->pNextChild_;
+ pPredecessor->pNextChild_ = pNext->pNextChild_;
+ }
+
+ if (pLastChild_ == pNext)
+ pLastChild_ = pPredecessor;
+
+ delete pNext;
+}
+
+void
+XmlElement::AddAttr(const QName & name, const std::string & value) {
+ ASSERT(!HasAttr(name));
+
+ XmlAttr ** pprev = pLastAttr_ ? &(pLastAttr_->pNextAttr_) : &pFirstAttr_;
+ pLastAttr_ = (*pprev = new XmlAttr(name, value));
+}
+
+void
+XmlElement::AddAttr(const QName & name, const std::string & value,
+ int depth) {
+ XmlElement * element = this;
+ while (depth--) {
+ element = element->pLastChild_->AsElement();
+ }
+ element->AddAttr(name, value);
+}
+
+void
+XmlElement::AddParsedText(const char * cstr, int len) {
+ if (len == 0)
+ return;
+
+ if (pLastChild_ && pLastChild_->IsText()) {
+ pLastChild_->AsText()->AddParsedText(cstr, len);
+ return;
+ }
+ XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+ pLastChild_ = *pprev = new XmlText(cstr, len);
+}
+
+void
+XmlElement::AddCDATAText(const char * buf, int len) {
+ cdata_ = true;
+ AddParsedText(buf, len);
+}
+
+void
+XmlElement::AddText(const std::string & text) {
+ if (text == STR_EMPTY)
+ return;
+
+ if (pLastChild_ && pLastChild_->IsText()) {
+ pLastChild_->AsText()->AddText(text);
+ return;
+ }
+ XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+ pLastChild_ = *pprev = new XmlText(text);
+}
+
+void
+XmlElement::AddText(const std::string & text, int depth) {
+ // note: the first syntax is ambigious for msvc 6
+ // XmlElement * pel(this);
+ XmlElement * element = this;
+ while (depth--) {
+ element = element->pLastChild_->AsElement();
+ }
+ element->AddText(text);
+}
+
+void
+XmlElement::AddElement(XmlElement *pelChild) {
+ if (pelChild == NULL)
+ return;
+
+ XmlChild ** pprev = pLastChild_ ? &(pLastChild_->pNextChild_) : &pFirstChild_;
+ pLastChild_ = *pprev = pelChild;
+ pelChild->pNextChild_ = NULL;
+}
+
+void
+XmlElement::AddElement(XmlElement *pelChild, int depth) {
+ XmlElement * element = this;
+ while (depth--) {
+ element = element->pLastChild_->AsElement();
+ }
+ element->AddElement(pelChild);
+}
+
+void
+XmlElement::ClearNamedChildren(const QName & name) {
+ XmlChild * prev_child = NULL;
+ XmlChild * next_child;
+ XmlChild * child;
+ for (child = FirstChild(); child; child = next_child) {
+ next_child = child->NextChild();
+ if (!child->IsText() && child->AsElement()->Name() == name)
+ {
+ RemoveChildAfter(prev_child);
+ continue;
+ }
+ prev_child = child;
+ }
+}
+
+void
+XmlElement::ClearAttributes() {
+ XmlAttr * pattr;
+ for (pattr = pFirstAttr_; pattr; ) {
+ XmlAttr * pToDelete = pattr;
+ pattr = pattr->pNextAttr_;
+ delete pToDelete;
+ }
+ pFirstAttr_ = pLastAttr_ = NULL;
+}
+
+void
+XmlElement::ClearChildren() {
+ XmlChild * pchild;
+ for (pchild = pFirstChild_; pchild; ) {
+ XmlChild * pToDelete = pchild;
+ pchild = pchild->pNextChild_;
+ delete pToDelete;
+ }
+ pFirstChild_ = pLastChild_ = NULL;
+}
+
+std::string
+XmlElement::Str() const {
+ std::stringstream ss;
+ Print(&ss, NULL, 0);
+ return ss.str();
+}
+
+XmlElement *
+XmlElement::ForStr(const std::string & str) {
+ XmlBuilder builder;
+ XmlParser::ParseXml(&builder, str);
+ return builder.CreateElement();
+}
+
+void
+XmlElement::Print(
+ std::ostream * pout, std::string xmlns[], int xmlnsCount) const {
+ XmlPrinter::PrintXml(pout, this, xmlns, xmlnsCount);
+}
+
+XmlElement::~XmlElement() {
+ XmlAttr * pattr;
+ for (pattr = pFirstAttr_; pattr; ) {
+ XmlAttr * pToDelete = pattr;
+ pattr = pattr->pNextAttr_;
+ delete pToDelete;
+ }
+
+ XmlChild * pchild;
+ for (pchild = pFirstChild_; pchild; ) {
+ XmlChild * pToDelete = pchild;
+ pchild = pchild->pNextChild_;
+ delete pToDelete;
+ }
+}
+
+}
diff --git a/talk/xmllite/xmlelement.h b/talk/xmllite/xmlelement.h
new file mode 100644
index 0000000..38e31a9
--- /dev/null
+++ b/talk/xmllite/xmlelement.h
@@ -0,0 +1,239 @@
+/*
+ * 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 _xmlelement_h_
+#define _xmlelement_h_
+
+#include <iosfwd>
+#include <string>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+extern const QName QN_EMPTY;
+extern const QName QN_XMLNS;
+
+
+class XmlChild;
+class XmlText;
+class XmlElement;
+class XmlAttr;
+
+class XmlChild {
+friend class XmlElement;
+
+public:
+
+ XmlChild * NextChild() { return pNextChild_; }
+ const XmlChild * NextChild() const { return pNextChild_; }
+
+ bool IsText() const { return IsTextImpl(); }
+
+ XmlElement * AsElement() { return AsElementImpl(); }
+ const XmlElement * AsElement() const { return AsElementImpl(); }
+
+ XmlText * AsText() { return AsTextImpl(); }
+ const XmlText * AsText() const { return AsTextImpl(); }
+
+
+protected:
+
+ XmlChild() :
+ pNextChild_(NULL) {
+ }
+
+ virtual bool IsTextImpl() const = 0;
+ virtual XmlElement * AsElementImpl() const = 0;
+ virtual XmlText * AsTextImpl() const = 0;
+
+
+ virtual ~XmlChild();
+
+private:
+ XmlChild(const XmlChild & noimpl);
+
+ XmlChild * pNextChild_;
+
+};
+
+class XmlText : public XmlChild {
+public:
+ explicit XmlText(const std::string & text) :
+ XmlChild(),
+ text_(text) {
+ }
+ explicit XmlText(const XmlText & t) :
+ XmlChild(),
+ text_(t.text_) {
+ }
+ explicit XmlText(const char * cstr, size_t len) :
+ XmlChild(),
+ text_(cstr, len) {
+ }
+ virtual ~XmlText();
+
+ const std::string & Text() const { return text_; }
+ void SetText(const std::string & text);
+ void AddParsedText(const char * buf, int len);
+ void AddText(const std::string & text);
+
+protected:
+ virtual bool IsTextImpl() const;
+ virtual XmlElement * AsElementImpl() const;
+ virtual XmlText * AsTextImpl() const;
+
+private:
+ std::string text_;
+};
+
+class XmlAttr {
+friend class XmlElement;
+
+public:
+ XmlAttr * NextAttr() const { return pNextAttr_; }
+ const QName & Name() const { return name_; }
+ const std::string & Value() const { return value_; }
+
+private:
+ explicit XmlAttr(const QName & name, const std::string & value) :
+ pNextAttr_(NULL),
+ name_(name),
+ value_(value) {
+ }
+ explicit XmlAttr(const XmlAttr & att) :
+ pNextAttr_(NULL),
+ name_(att.name_),
+ value_(att.value_) {
+ }
+
+ XmlAttr * pNextAttr_;
+ QName name_;
+ std::string value_;
+};
+
+class XmlElement : public XmlChild {
+public:
+ explicit XmlElement(const QName & name);
+ explicit XmlElement(const QName & name, bool useDefaultNs);
+ explicit XmlElement(const XmlElement & elt);
+
+ virtual ~XmlElement();
+
+ const QName& Name() const { return name_; }
+ void SetName(const QName& name) { name_ = name; }
+
+ const std::string & BodyText() const;
+ void SetBodyText(const std::string & text);
+
+ const QName & FirstElementName() const;
+
+ XmlAttr * FirstAttr();
+ const XmlAttr * FirstAttr() const
+ { return const_cast<XmlElement *>(this)->FirstAttr(); }
+
+ //! Attr will return STR_EMPTY if the attribute isn't there:
+ //! use HasAttr to test presence of an attribute.
+ const std::string & Attr(const QName & name) const;
+ bool HasAttr(const QName & name) const;
+ void SetAttr(const QName & name, const std::string & value);
+ void ClearAttr(const QName & name);
+
+ XmlChild * FirstChild();
+ const XmlChild * FirstChild() const
+ { return const_cast<XmlElement *>(this)->FirstChild(); }
+
+ XmlElement * FirstElement();
+ const XmlElement * FirstElement() const
+ { return const_cast<XmlElement *>(this)->FirstElement(); }
+
+ XmlElement * NextElement();
+ const XmlElement * NextElement() const
+ { return const_cast<XmlElement *>(this)->NextElement(); }
+
+ XmlElement * FirstWithNamespace(const std::string & ns);
+ const XmlElement * FirstWithNamespace(const std::string & ns) const
+ { return const_cast<XmlElement *>(this)->FirstWithNamespace(ns); }
+
+ XmlElement * NextWithNamespace(const std::string & ns);
+ const XmlElement * NextWithNamespace(const std::string & ns) const
+ { return const_cast<XmlElement *>(this)->NextWithNamespace(ns); }
+
+ XmlElement * FirstNamed(const QName & name);
+ const XmlElement * FirstNamed(const QName & name) const
+ { return const_cast<XmlElement *>(this)->FirstNamed(name); }
+
+ XmlElement * NextNamed(const QName & name);
+ const XmlElement * NextNamed(const QName & name) const
+ { return const_cast<XmlElement *>(this)->NextNamed(name); }
+
+ // Finds the first element named 'name'. If that element can't be found then
+ // adds one and returns it.
+ XmlElement* FindOrAddNamedChild(const QName& name);
+
+ const std::string & TextNamed(const QName & name) const;
+
+ void InsertChildAfter(XmlChild * pPredecessor, XmlChild * pNewChild);
+ void RemoveChildAfter(XmlChild * pPredecessor);
+
+ void AddParsedText(const char * buf, int len);
+ // Note: CDATA is not supported by XMPP, therefore using this function will
+ // generate non-XMPP compatible XML.
+ void AddCDATAText(const char * buf, int len);
+ void AddText(const std::string & text);
+ void AddText(const std::string & text, int depth);
+ void AddElement(XmlElement * pelChild);
+ void AddElement(XmlElement * pelChild, int depth);
+ void AddAttr(const QName & name, const std::string & value);
+ void AddAttr(const QName & name, const std::string & value, int depth);
+ void ClearNamedChildren(const QName & name);
+ void ClearAttributes();
+ void ClearChildren();
+
+ static XmlElement * ForStr(const std::string & str);
+ std::string Str() const;
+
+ void Print(std::ostream * pout, std::string xmlns[], int xmlnsCount) const;
+
+ bool IsCDATA() const { return cdata_; }
+
+protected:
+ virtual bool IsTextImpl() const;
+ virtual XmlElement * AsElementImpl() const;
+ virtual XmlText * AsTextImpl() const;
+
+private:
+ QName name_;
+ XmlAttr * pFirstAttr_;
+ XmlAttr * pLastAttr_;
+ XmlChild * pFirstChild_;
+ XmlChild * pLastChild_;
+ bool cdata_;
+};
+
+}
+#endif
diff --git a/talk/xmllite/xmlnsstack.cc b/talk/xmllite/xmlnsstack.cc
new file mode 100644
index 0000000..18e1607
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.cc
@@ -0,0 +1,204 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+XmlnsStack::XmlnsStack() :
+ pxmlnsStack_(new std::vector<std::string>),
+ pxmlnsDepthStack_(new std::vector<size_t>) {
+}
+
+XmlnsStack::~XmlnsStack() {}
+
+void
+XmlnsStack::PushFrame() {
+ pxmlnsDepthStack_->push_back(pxmlnsStack_->size());
+}
+
+void
+XmlnsStack::PopFrame() {
+ size_t prev_size = pxmlnsDepthStack_->back();
+ pxmlnsDepthStack_->pop_back();
+ if (prev_size < pxmlnsStack_->size()) {
+ pxmlnsStack_->erase(pxmlnsStack_->begin() + prev_size,
+ pxmlnsStack_->end());
+ }
+}
+const std::pair<std::string, bool> NS_NOT_FOUND(STR_EMPTY, false);
+const std::pair<std::string, bool> EMPTY_NS_FOUND(STR_EMPTY, true);
+const std::pair<std::string, bool> XMLNS_DEFINITION_FOUND(NS_XMLNS, true);
+
+const std::string *
+XmlnsStack::NsForPrefix(const std::string & prefix) {
+ if (prefix.length() >= 3 &&
+ (prefix[0] == 'x' || prefix[0] == 'X') &&
+ (prefix[1] == 'm' || prefix[1] == 'M') &&
+ (prefix[2] == 'l' || prefix[2] == 'L')) {
+ if (prefix == "xml")
+ return &(NS_XML);
+ if (prefix == "xmlns")
+ return &(NS_XMLNS);
+ return NULL;
+ }
+
+ std::vector<std::string>::iterator pos;
+ for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
+ pos -= 2;
+ if (*pos == prefix)
+ return &(*(pos + 1));
+ }
+
+ if (prefix == STR_EMPTY)
+ return &(STR_EMPTY); // default namespace
+
+ return NULL; // none found
+}
+
+bool
+XmlnsStack::PrefixMatchesNs(const std::string & prefix, const std::string & ns) {
+ const std::string * match = NsForPrefix(prefix);
+ if (match == NULL)
+ return false;
+ return (*match == ns);
+}
+
+std::pair<std::string, bool>
+XmlnsStack::PrefixForNs(const std::string & ns, bool isattr) {
+ if (ns == NS_XML)
+ return std::make_pair(std::string("xml"), true);
+ if (ns == NS_XMLNS)
+ return std::make_pair(std::string("xmlns"), true);
+ if (isattr ? ns == STR_EMPTY : PrefixMatchesNs(STR_EMPTY, ns))
+ return std::make_pair(STR_EMPTY, true);
+
+ std::vector<std::string>::iterator pos;
+ for (pos = pxmlnsStack_->end(); pos > pxmlnsStack_->begin(); ) {
+ pos -= 2;
+ if (*(pos + 1) == ns &&
+ (!isattr || !pos->empty()) && PrefixMatchesNs(*pos, ns))
+ return std::make_pair(*pos, true);
+ }
+
+ return std::make_pair(STR_EMPTY, false); // none found
+}
+
+std::string
+XmlnsStack::FormatQName(const QName & name, bool isAttr) {
+ std::string prefix(PrefixForNs(name.Namespace(), isAttr).first);
+ if (prefix == STR_EMPTY)
+ return name.LocalPart();
+ else
+ return prefix + ':' + name.LocalPart();
+}
+
+void
+XmlnsStack::AddXmlns(const std::string & prefix, const std::string & ns) {
+ pxmlnsStack_->push_back(prefix);
+ pxmlnsStack_->push_back(ns);
+}
+
+void
+XmlnsStack::RemoveXmlns() {
+ pxmlnsStack_->pop_back();
+ pxmlnsStack_->pop_back();
+}
+
+static bool IsAsciiLetter(char ch) {
+ return ((ch >= 'a' && ch <= 'z') ||
+ (ch >= 'A' && ch <= 'Z'));
+}
+
+static std::string AsciiLower(const std::string & s) {
+ std::string result(s);
+ size_t i;
+ for (i = 0; i < result.length(); i++) {
+ if (result[i] >= 'A' && result[i] <= 'Z')
+ result[i] += 'a' - 'A';
+ }
+ return result;
+}
+
+static std::string SuggestPrefix(const std::string & ns) {
+ size_t len = ns.length();
+ size_t i = ns.find_last_of('.');
+ if (i != std::string::npos && len - i <= 4 + 1)
+ len = i; // chop off ".html" or ".xsd" or ".?{0,4}"
+ size_t last = len;
+ while (last > 0) {
+ last -= 1;
+ if (IsAsciiLetter(ns[last])) {
+ size_t first = last;
+ last += 1;
+ while (first > 0) {
+ if (!IsAsciiLetter(ns[first - 1]))
+ break;
+ first -= 1;
+ }
+ if (last - first > 4)
+ last = first + 3;
+ std::string candidate(AsciiLower(ns.substr(first, last - first)));
+ if (candidate.find("xml") != 0)
+ return candidate;
+ break;
+ }
+ }
+ return "ns";
+}
+
+
+std::pair<std::string, bool>
+XmlnsStack::AddNewPrefix(const std::string & ns, bool isAttr) {
+ if (PrefixForNs(ns, isAttr).second)
+ return std::make_pair(STR_EMPTY, false);
+
+ std::string base(SuggestPrefix(ns));
+ std::string result(base);
+ int i = 2;
+ while (NsForPrefix(result) != NULL) {
+ std::stringstream ss;
+ ss << base;
+ ss << (i++);
+ ss >> result;
+ }
+ AddXmlns(result, ns);
+ return std::make_pair(result, true);
+}
+
+void XmlnsStack::Reset() {
+ pxmlnsStack_->clear();
+ pxmlnsDepthStack_->clear();
+}
+
+}
diff --git a/talk/xmllite/xmlnsstack.h b/talk/xmllite/xmlnsstack.h
new file mode 100644
index 0000000..c7e9f89
--- /dev/null
+++ b/talk/xmllite/xmlnsstack.h
@@ -0,0 +1,62 @@
+/*
+ * 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 _xmlnsstack_h_
+#define _xmlnsstack_h_
+
+#include <string>
+#include <vector>
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmllite/qname.h"
+
+namespace buzz {
+
+class XmlnsStack {
+public:
+ XmlnsStack();
+ ~XmlnsStack();
+
+ void AddXmlns(const std::string & prefix, const std::string & ns);
+ void RemoveXmlns();
+ void PushFrame();
+ void PopFrame();
+ void Reset();
+
+ const std::string * NsForPrefix(const std::string & prefix);
+ bool PrefixMatchesNs(const std::string & prefix, const std::string & ns);
+ std::pair<std::string, bool> PrefixForNs(const std::string & ns, bool isAttr);
+ std::pair<std::string, bool> AddNewPrefix(const std::string & ns, bool isAttr);
+ std::string FormatQName(const QName & name, bool isAttr);
+
+private:
+
+ talk_base::scoped_ptr<std::vector<std::string> > pxmlnsStack_;
+ talk_base::scoped_ptr<std::vector<size_t> > pxmlnsDepthStack_;
+};
+}
+
+#endif
diff --git a/talk/xmllite/xmlparser.cc b/talk/xmllite/xmlparser.cc
new file mode 100644
index 0000000..568bce1
--- /dev/null
+++ b/talk/xmllite/xmlparser.cc
@@ -0,0 +1,292 @@
+/*
+ * 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 "talk/xmllite/xmlparser.h"
+
+#include <string>
+#include <vector>
+#include <iostream>
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlnsstack.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif // EXPAT_RELATIVE_PATH
+
+namespace buzz {
+
+
+static void
+StartElementCallback(void * userData, const char *name, const char **atts) {
+ (static_cast<XmlParser *>(userData))->ExpatStartElement(name, atts);
+}
+
+static void
+EndElementCallback(void * userData, const char *name) {
+ (static_cast<XmlParser *>(userData))->ExpatEndElement(name);
+}
+
+static void
+CharacterDataCallback(void * userData, const char *text, int len) {
+ (static_cast<XmlParser *>(userData))->ExpatCharacterData(text, len);
+}
+
+static void
+XmlDeclCallback(void * userData, const char * ver, const char * enc, int st) {
+ (static_cast<XmlParser *>(userData))->ExpatXmlDecl(ver, enc, st);
+}
+
+XmlParser::XmlParser(XmlParseHandler *pxph) :
+ context_(this), pxph_(pxph), sentError_(false) {
+ expat_ = XML_ParserCreate(NULL);
+ XML_SetUserData(expat_, this);
+ XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+ XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+ XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+}
+
+void
+XmlParser::Reset() {
+ if (!XML_ParserReset(expat_, NULL)) {
+ XML_ParserFree(expat_);
+ expat_ = XML_ParserCreate(NULL);
+ }
+ XML_SetUserData(expat_, this);
+ XML_SetElementHandler(expat_, StartElementCallback, EndElementCallback);
+ XML_SetCharacterDataHandler(expat_, CharacterDataCallback);
+ XML_SetXmlDeclHandler(expat_, XmlDeclCallback);
+ context_.Reset();
+ sentError_ = false;
+}
+
+static bool
+XmlParser_StartsWithXmlns(const char *name) {
+ return name[0] == 'x' &&
+ name[1] == 'm' &&
+ name[2] == 'l' &&
+ name[3] == 'n' &&
+ name[4] == 's';
+}
+
+void
+XmlParser::ExpatStartElement(const char *name, const char **atts) {
+ if (context_.RaisedError() != XML_ERROR_NONE)
+ return;
+ const char **att;
+ context_.StartElement();
+ for (att = atts; *att; att += 2) {
+ if (XmlParser_StartsWithXmlns(*att)) {
+ if ((*att)[5] == '\0') {
+ context_.StartNamespace("", *(att + 1));
+ }
+ else if ((*att)[5] == ':') {
+ if (**(att + 1) == '\0') {
+ // In XML 1.0 empty namespace illegal with prefix (not in 1.1)
+ context_.RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+ context_.StartNamespace((*att) + 6, *(att + 1));
+ }
+ }
+ }
+ context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+ XML_GetCurrentColumnNumber(expat_),
+ XML_GetCurrentByteIndex(expat_));
+ pxph_->StartElement(&context_, name, atts);
+}
+
+void
+XmlParser::ExpatEndElement(const char *name) {
+ if (context_.RaisedError() != XML_ERROR_NONE)
+ return;
+ context_.EndElement();
+ context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+ XML_GetCurrentColumnNumber(expat_),
+ XML_GetCurrentByteIndex(expat_));
+ pxph_->EndElement(&context_, name);
+}
+
+void
+XmlParser::ExpatCharacterData(const char *text, int len) {
+ if (context_.RaisedError() != XML_ERROR_NONE)
+ return;
+ context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+ XML_GetCurrentColumnNumber(expat_),
+ XML_GetCurrentByteIndex(expat_));
+ pxph_->CharacterData(&context_, text, len);
+}
+
+void
+XmlParser::ExpatXmlDecl(const char * ver, const char * enc, int standalone) {
+ if (context_.RaisedError() != XML_ERROR_NONE)
+ return;
+
+ if (ver && std::string("1.0") != ver) {
+ context_.RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+
+ if (standalone == 0) {
+ context_.RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+
+ if (enc && !((enc[0] == 'U' || enc[0] == 'u') &&
+ (enc[1] == 'T' || enc[1] == 't') &&
+ (enc[2] == 'F' || enc[2] == 'f') &&
+ enc[3] == '-' && enc[4] =='8')) {
+ context_.RaiseError(XML_ERROR_INCORRECT_ENCODING);
+ return;
+ }
+
+}
+
+bool
+XmlParser::Parse(const char *data, size_t len, bool isFinal) {
+ if (sentError_)
+ return false;
+
+ if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) !=
+ XML_STATUS_OK) {
+ context_.SetPosition(XML_GetCurrentLineNumber(expat_),
+ XML_GetCurrentColumnNumber(expat_),
+ XML_GetCurrentByteIndex(expat_));
+ context_.RaiseError(XML_GetErrorCode(expat_));
+ }
+
+ if (context_.RaisedError() != XML_ERROR_NONE) {
+ sentError_ = true;
+ pxph_->Error(&context_, context_.RaisedError());
+ return false;
+ }
+
+ return true;
+}
+
+XmlParser::~XmlParser() {
+ XML_ParserFree(expat_);
+}
+
+void
+XmlParser::ParseXml(XmlParseHandler *pxph, std::string text) {
+ XmlParser parser(pxph);
+ parser.Parse(text.c_str(), text.length(), true);
+}
+
+XmlParser::ParseContext::ParseContext(XmlParser *parser) :
+ parser_(parser),
+ xmlnsstack_(),
+ raised_(XML_ERROR_NONE),
+ line_number_(0),
+ column_number_(0),
+ byte_index_(0) {
+}
+
+void
+XmlParser::ParseContext::StartNamespace(const char *prefix, const char *ns) {
+ xmlnsstack_.AddXmlns(
+ *prefix ? std::string(prefix) : STR_EMPTY,
+// ns == NS_CLIENT ? NS_CLIENT :
+// ns == NS_ROSTER ? NS_ROSTER :
+// ns == NS_GR ? NS_GR :
+ std::string(ns));
+}
+
+void
+XmlParser::ParseContext::StartElement() {
+ xmlnsstack_.PushFrame();
+}
+
+void
+XmlParser::ParseContext::EndElement() {
+ xmlnsstack_.PopFrame();
+}
+
+QName
+XmlParser::ParseContext::ResolveQName(const char *qname, bool isAttr) {
+ const char *c;
+ for (c = qname; *c; ++c) {
+ if (*c == ':') {
+ const std::string * result;
+ result = xmlnsstack_.NsForPrefix(std::string(qname, c - qname));
+ if (result == NULL)
+ return QN_EMPTY;
+ const char * localname = c + 1;
+ return QName(*result, localname);
+ }
+ }
+ if (isAttr) {
+ return QName(STR_EMPTY, qname);
+ }
+
+ const std::string * result;
+ result = xmlnsstack_.NsForPrefix(STR_EMPTY);
+ if (result == NULL)
+ return QN_EMPTY;
+
+ return QName(*result, qname);
+}
+
+void
+XmlParser::ParseContext::Reset() {
+ xmlnsstack_.Reset();
+ raised_ = XML_ERROR_NONE;
+}
+
+void
+XmlParser::ParseContext::SetPosition(int line, int column,
+ long byte_index) {
+ line_number_ = line;
+ column_number_ = column;
+ byte_index_ = byte_index;
+}
+
+void
+XmlParser::ParseContext::GetPosition(unsigned long * line,
+ unsigned long * column,
+ unsigned long * byte_index) {
+ if (line != NULL) {
+ *line = static_cast<unsigned long>(line_number_);
+ }
+
+ if (column != NULL) {
+ *column = static_cast<unsigned long>(column_number_);
+ }
+
+ if (byte_index != NULL) {
+ *byte_index = static_cast<unsigned long>(byte_index_);
+ }
+}
+
+XmlParser::ParseContext::~ParseContext() {
+}
+
+}
diff --git a/talk/xmllite/xmlparser.h b/talk/xmllite/xmlparser.h
new file mode 100644
index 0000000..3e85e35
--- /dev/null
+++ b/talk/xmllite/xmlparser.h
@@ -0,0 +1,121 @@
+/*
+ * 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 _xmlparser_h_
+#define _xmlparser_h_
+
+#include <string>
+
+#include "talk/xmllite/xmlnsstack.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif // EXPAT_RELATIVE_PATH
+
+struct XML_ParserStruct;
+typedef struct XML_ParserStruct* XML_Parser;
+
+namespace buzz {
+
+class XmlParseHandler;
+class XmlParseContext;
+class XmlParser;
+
+class XmlParseContext {
+public:
+ virtual ~XmlParseContext() {}
+ virtual QName ResolveQName(const char * qname, bool isAttr) = 0;
+ virtual void RaiseError(XML_Error err) = 0;
+ virtual void GetPosition(unsigned long * line, unsigned long * column,
+ unsigned long * byte_index) = 0;
+};
+
+class XmlParseHandler {
+public:
+ virtual ~XmlParseHandler() {}
+ virtual void StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts) = 0;
+ virtual void EndElement(XmlParseContext * pctx,
+ const char * name) = 0;
+ virtual void CharacterData(XmlParseContext * pctx,
+ const char * text, int len) = 0;
+ virtual void Error(XmlParseContext * pctx,
+ XML_Error errorCode) = 0;
+};
+
+class XmlParser {
+public:
+ static void ParseXml(XmlParseHandler * pxph, std::string text);
+
+ explicit XmlParser(XmlParseHandler * pxph);
+ bool Parse(const char * data, size_t len, bool isFinal);
+ void Reset();
+ virtual ~XmlParser();
+
+ // expat callbacks
+ void ExpatStartElement(const char * name, const char ** atts);
+ void ExpatEndElement(const char * name);
+ void ExpatCharacterData(const char * text, int len);
+ void ExpatXmlDecl(const char * ver, const char * enc, int standalone);
+
+private:
+
+ class ParseContext : public XmlParseContext {
+ public:
+ ParseContext(XmlParser * parser);
+ virtual ~ParseContext();
+ virtual QName ResolveQName(const char * qname, bool isAttr);
+ virtual void RaiseError(XML_Error err) { if (!raised_) raised_ = err; }
+ virtual void GetPosition(unsigned long * line, unsigned long * column,
+ unsigned long * byte_index);
+ XML_Error RaisedError() { return raised_; }
+ void Reset();
+
+ void StartElement();
+ void EndElement();
+ void StartNamespace(const char * prefix, const char * ns);
+ void SetPosition(int line, int column, long byte_index);
+
+ private:
+ const XmlParser * parser_;
+ XmlnsStack xmlnsstack_;
+ XML_Error raised_;
+ XML_Size line_number_;
+ XML_Size column_number_;
+ XML_Index byte_index_;
+ };
+
+ ParseContext context_;
+ XML_Parser expat_;
+ XmlParseHandler * pxph_;
+ bool sentError_;
+};
+
+}
+
+#endif
diff --git a/talk/xmllite/xmlprinter.cc b/talk/xmllite/xmlprinter.cc
new file mode 100644
index 0000000..b05898f
--- /dev/null
+++ b/talk/xmllite/xmlprinter.cc
@@ -0,0 +1,198 @@
+/*
+ * 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 <string>
+#include <iostream>
+#include <vector>
+#include <sstream>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmllite/xmlnsstack.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+class XmlPrinterImpl {
+public:
+ XmlPrinterImpl(std::ostream * pout,
+ const std::string * const xmlns, int xmlnsCount);
+ void PrintElement(const XmlElement * element);
+ void PrintQuotedValue(const std::string & text);
+ void PrintBodyText(const std::string & text);
+ void PrintCDATAText(const std::string & text);
+
+private:
+ std::ostream *pout_;
+ XmlnsStack xmlnsStack_;
+};
+
+void
+XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element) {
+ PrintXml(pout, element, NULL, 0);
+}
+
+void
+XmlPrinter::PrintXml(std::ostream * pout, const XmlElement * element,
+ const std::string * const xmlns, int xmlnsCount) {
+ XmlPrinterImpl printer(pout, xmlns, xmlnsCount);
+ printer.PrintElement(element);
+}
+
+XmlPrinterImpl::XmlPrinterImpl(std::ostream * pout,
+ const std::string * const xmlns, int xmlnsCount) :
+ pout_(pout),
+ xmlnsStack_() {
+ int i;
+ for (i = 0; i < xmlnsCount; i += 2) {
+ xmlnsStack_.AddXmlns(xmlns[i], xmlns[i + 1]);
+ }
+}
+
+void
+XmlPrinterImpl::PrintElement(const XmlElement * element) {
+ xmlnsStack_.PushFrame();
+
+ // first go through attrs of pel to add xmlns definitions
+ const XmlAttr * pattr;
+ for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+ if (pattr->Name() == QN_XMLNS)
+ xmlnsStack_.AddXmlns(STR_EMPTY, pattr->Value());
+ else if (pattr->Name().Namespace() == NS_XMLNS)
+ xmlnsStack_.AddXmlns(pattr->Name().LocalPart(),
+ pattr->Value());
+ }
+
+ // then go through qnames to make sure needed xmlns definitons are added
+ std::vector<std::string> newXmlns;
+ std::pair<std::string, bool> prefix;
+ prefix = xmlnsStack_.AddNewPrefix(element->Name().Namespace(), false);
+ if (prefix.second) {
+ newXmlns.push_back(prefix.first);
+ newXmlns.push_back(element->Name().Namespace());
+ }
+
+ for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+ prefix = xmlnsStack_.AddNewPrefix(pattr->Name().Namespace(), true);
+ if (prefix.second) {
+ newXmlns.push_back(prefix.first);
+ newXmlns.push_back(pattr->Name().Namespace());
+ }
+ }
+
+ // print the element name
+ *pout_ << '<' << xmlnsStack_.FormatQName(element->Name(), false);
+
+ // and the attributes
+ for (pattr = element->FirstAttr(); pattr; pattr = pattr->NextAttr()) {
+ *pout_ << ' ' << xmlnsStack_.FormatQName(pattr->Name(), true) << "=\"";
+ PrintQuotedValue(pattr->Value());
+ *pout_ << '"';
+ }
+
+ // and the extra xmlns declarations
+ std::vector<std::string>::iterator i(newXmlns.begin());
+ while (i < newXmlns.end()) {
+ if (*i == STR_EMPTY)
+ *pout_ << " xmlns=\"" << *(i + 1) << '"';
+ else
+ *pout_ << " xmlns:" << *i << "=\"" << *(i + 1) << '"';
+ i += 2;
+ }
+
+ // now the children
+ const XmlChild * pchild = element->FirstChild();
+
+ if (pchild == NULL)
+ *pout_ << "/>";
+ else {
+ *pout_ << '>';
+ while (pchild) {
+ if (pchild->IsText()) {
+ if (element->IsCDATA()) {
+ PrintCDATAText(pchild->AsText()->Text());
+ } else {
+ PrintBodyText(pchild->AsText()->Text());
+ }
+ } else
+ PrintElement(pchild->AsElement());
+ pchild = pchild->NextChild();
+ }
+ *pout_ << "</" << xmlnsStack_.FormatQName(element->Name(), false) << '>';
+ }
+
+ xmlnsStack_.PopFrame();
+}
+
+void
+XmlPrinterImpl::PrintQuotedValue(const std::string & text) {
+ size_t safe = 0;
+ for (;;) {
+ size_t unsafe = text.find_first_of("<>&\"", safe);
+ if (unsafe == std::string::npos)
+ unsafe = text.length();
+ *pout_ << text.substr(safe, unsafe - safe);
+ if (unsafe == text.length())
+ return;
+ switch (text[unsafe]) {
+ case '<': *pout_ << "<"; break;
+ case '>': *pout_ << ">"; break;
+ case '&': *pout_ << "&"; break;
+ case '"': *pout_ << """; break;
+ }
+ safe = unsafe + 1;
+ if (safe == text.length())
+ return;
+ }
+}
+
+void
+XmlPrinterImpl::PrintBodyText(const std::string & text) {
+ size_t safe = 0;
+ for (;;) {
+ size_t unsafe = text.find_first_of("<>&", safe);
+ if (unsafe == std::string::npos)
+ unsafe = text.length();
+ *pout_ << text.substr(safe, unsafe - safe);
+ if (unsafe == text.length())
+ return;
+ switch (text[unsafe]) {
+ case '<': *pout_ << "<"; break;
+ case '>': *pout_ << ">"; break;
+ case '&': *pout_ << "&"; break;
+ }
+ safe = unsafe + 1;
+ if (safe == text.length())
+ return;
+ }
+}
+
+void
+XmlPrinterImpl::PrintCDATAText(const std::string & text) {
+ *pout_ << "<![CDATA[" << text << "]]>";
+}
+
+}
diff --git a/talk/xmllite/xmlprinter.h b/talk/xmllite/xmlprinter.h
new file mode 100644
index 0000000..96900d0
--- /dev/null
+++ b/talk/xmllite/xmlprinter.h
@@ -0,0 +1,49 @@
+/*
+ * 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 _xmlprinter_h_
+#define _xmlprinter_h_
+
+#include <iosfwd>
+#include <string>
+#include "talk/base/scoped_ptr.h"
+
+namespace buzz {
+
+class XmlElement;
+
+class XmlPrinter {
+public:
+ static void PrintXml(std::ostream * pout, const XmlElement * pelt);
+
+ static void PrintXml(std::ostream * pout, const XmlElement * pelt,
+ const std::string * const xmlns, int xmlnsCount);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/asyncsocket.h b/talk/xmpp/asyncsocket.h
new file mode 100644
index 0000000..e4bce7f
--- /dev/null
+++ b/talk/xmpp/asyncsocket.h
@@ -0,0 +1,86 @@
+/*
+ * 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 _ASYNCSOCKET_H_
+#define _ASYNCSOCKET_H_
+
+#include "talk/base/sigslot.h"
+
+namespace talk_base {
+ class SocketAddress;
+}
+
+namespace buzz {
+
+class AsyncSocket {
+public:
+ enum State {
+ STATE_CLOSED = 0, //!< Socket is not open.
+ STATE_CLOSING, //!< Socket is closing but can have buffered data
+ STATE_CONNECTING, //!< In the process of
+ STATE_OPEN, //!< Socket is connected
+#if defined(FEATURE_ENABLE_SSL)
+ STATE_TLS_CONNECTING, //!< Establishing TLS connection
+ STATE_TLS_OPEN, //!< TLS connected
+#endif
+ };
+
+ enum Error {
+ ERROR_NONE = 0, //!< No error
+ ERROR_WINSOCK, //!< Winsock error
+ ERROR_DNS, //!< Couldn't resolve host name
+ ERROR_WRONGSTATE, //!< Call made while socket is in the wrong state
+#if defined(FEATURE_ENABLE_SSL)
+ ERROR_SSL, //!< Something went wrong with OpenSSL
+#endif
+ };
+
+ virtual ~AsyncSocket() {}
+ virtual State state() = 0;
+ virtual Error error() = 0;
+ virtual int GetError() = 0; // winsock error code
+
+ virtual bool Connect(const talk_base::SocketAddress& addr) = 0;
+ virtual bool Read(char * data, size_t len, size_t* len_read) = 0;
+ virtual bool Write(const char * data, size_t len) = 0;
+ virtual bool Close() = 0;
+#if defined(FEATURE_ENABLE_SSL)
+ // We allow matching any passed domain.
+ // If both names are passed as empty, we do not require a match.
+ virtual bool StartTls(const std::string & domainname) = 0;
+#endif
+
+ sigslot::signal0<> SignalConnected;
+ sigslot::signal0<> SignalSSLConnected;
+ sigslot::signal0<> SignalClosed;
+ sigslot::signal0<> SignalRead;
+ sigslot::signal0<> SignalError;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/constants.cc b/talk/xmpp/constants.cc
new file mode 100644
index 0000000..aa581f4
--- /dev/null
+++ b/talk/xmpp/constants.cc
@@ -0,0 +1,467 @@
+/*
+ * 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 <string>
+#include "talk/base/basicdefs.h"
+#include "talk/xmllite/xmlconstants.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/constants.h"
+namespace buzz {
+
+const Jid JID_EMPTY(STR_EMPTY);
+
+const std::string & Constants::ns_client() {
+ static const std::string ns_client_("jabber:client");
+ return ns_client_;
+}
+
+const std::string & Constants::ns_server() {
+ static const std::string ns_server_("jabber:server");
+ return ns_server_;
+}
+
+const std::string & Constants::ns_stream() {
+ static const std::string ns_stream_("http://etherx.jabber.org/streams");
+ return ns_stream_;
+}
+
+const std::string & Constants::ns_xstream() {
+ static const std::string ns_xstream_("urn:ietf:params:xml:ns:xmpp-streams");
+ return ns_xstream_;
+}
+
+const std::string & Constants::ns_tls() {
+ static const std::string ns_tls_("urn:ietf:params:xml:ns:xmpp-tls");
+ return ns_tls_;
+}
+
+const std::string & Constants::ns_sasl() {
+ static const std::string ns_sasl_("urn:ietf:params:xml:ns:xmpp-sasl");
+ return ns_sasl_;
+}
+
+const std::string & Constants::ns_bind() {
+ static const std::string ns_bind_("urn:ietf:params:xml:ns:xmpp-bind");
+ return ns_bind_;
+}
+
+const std::string & Constants::ns_dialback() {
+ static const std::string ns_dialback_("jabber:server:dialback");
+ return ns_dialback_;
+}
+
+const std::string & Constants::ns_session() {
+ static const std::string ns_session_("urn:ietf:params:xml:ns:xmpp-session");
+ return ns_session_;
+}
+
+const std::string & Constants::ns_stanza() {
+ static const std::string ns_stanza_("urn:ietf:params:xml:ns:xmpp-stanzas");
+ return ns_stanza_;
+}
+
+const std::string & Constants::ns_privacy() {
+ static const std::string ns_privacy_("jabber:iq:privacy");
+ return ns_privacy_;
+}
+
+const std::string & Constants::ns_roster() {
+ static const std::string ns_roster_("jabber:iq:roster");
+ return ns_roster_;
+}
+
+const std::string & Constants::ns_vcard() {
+ static const std::string ns_vcard_("vcard-temp");
+ return ns_vcard_;
+}
+
+const std::string & Constants::ns_avatar_hash() {
+ static const std::string ns_avatar_hash_("google:avatar");
+ return ns_avatar_hash_;
+}
+
+const std::string & Constants::ns_vcard_update() {
+ static const std::string ns_vcard_update_("vcard-temp:x:update");
+ return ns_vcard_update_;
+}
+
+const std::string & Constants::str_client() {
+ static const std::string str_client_("client");
+ return str_client_;
+}
+
+const std::string & Constants::str_server() {
+ static const std::string str_server_("server");
+ return str_server_;
+}
+
+const std::string & Constants::str_stream() {
+ static const std::string str_stream_("stream");
+ return str_stream_;
+}
+
+const std::string STR_GET("get");
+const std::string STR_SET("set");
+const std::string STR_RESULT("result");
+const std::string STR_ERROR("error");
+
+
+const std::string STR_FROM("from");
+const std::string STR_TO("to");
+const std::string STR_BOTH("both");
+const std::string STR_REMOVE("remove");
+
+const std::string STR_UNAVAILABLE("unavailable");
+
+const std::string STR_GOOGLE_COM("google.com");
+const std::string STR_GMAIL_COM("gmail.com");
+const std::string STR_GOOGLEMAIL_COM("googlemail.com");
+const std::string STR_DEFAULT_DOMAIN("default.talk.google.com");
+const std::string STR_TALK_GOOGLE_COM("talk.google.com");
+const std::string STR_TALKX_L_GOOGLE_COM("talkx.l.google.com");
+
+const std::string STR_X("x");
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+const std::string STR_VOICEMAIL("voicemail");
+const std::string STR_OUTGOINGVOICEMAIL("outgoingvoicemail");
+#endif
+
+const QName QN_STREAM_STREAM(true, NS_STREAM, STR_STREAM);
+const QName QN_STREAM_FEATURES(true, NS_STREAM, "features");
+const QName QN_STREAM_ERROR(true, NS_STREAM, "error");
+
+const QName QN_XSTREAM_BAD_FORMAT(true, NS_XSTREAM, "bad-format");
+const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX(true, NS_XSTREAM, "bad-namespace-prefix");
+const QName QN_XSTREAM_CONFLICT(true, NS_XSTREAM, "conflict");
+const QName QN_XSTREAM_CONNECTION_TIMEOUT(true, NS_XSTREAM, "connection-timeout");
+const QName QN_XSTREAM_HOST_GONE(true, NS_XSTREAM, "host-gone");
+const QName QN_XSTREAM_HOST_UNKNOWN(true, NS_XSTREAM, "host-unknown");
+const QName QN_XSTREAM_IMPROPER_ADDRESSIING(true, NS_XSTREAM, "improper-addressing");
+const QName QN_XSTREAM_INTERNAL_SERVER_ERROR(true, NS_XSTREAM, "internal-server-error");
+const QName QN_XSTREAM_INVALID_FROM(true, NS_XSTREAM, "invalid-from");
+const QName QN_XSTREAM_INVALID_ID(true, NS_XSTREAM, "invalid-id");
+const QName QN_XSTREAM_INVALID_NAMESPACE(true, NS_XSTREAM, "invalid-namespace");
+const QName QN_XSTREAM_INVALID_XML(true, NS_XSTREAM, "invalid-xml");
+const QName QN_XSTREAM_NOT_AUTHORIZED(true, NS_XSTREAM, "not-authorized");
+const QName QN_XSTREAM_POLICY_VIOLATION(true, NS_XSTREAM, "policy-violation");
+const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED(true, NS_XSTREAM, "remote-connection-failed");
+const QName QN_XSTREAM_RESOURCE_CONSTRAINT(true, NS_XSTREAM, "resource-constraint");
+const QName QN_XSTREAM_RESTRICTED_XML(true, NS_XSTREAM, "restricted-xml");
+const QName QN_XSTREAM_SEE_OTHER_HOST(true, NS_XSTREAM, "see-other-host");
+const QName QN_XSTREAM_SYSTEM_SHUTDOWN(true, NS_XSTREAM, "system-shutdown");
+const QName QN_XSTREAM_UNDEFINED_CONDITION(true, NS_XSTREAM, "undefined-condition");
+const QName QN_XSTREAM_UNSUPPORTED_ENCODING(true, NS_XSTREAM, "unsupported-encoding");
+const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE(true, NS_XSTREAM, "unsupported-stanza-type");
+const QName QN_XSTREAM_UNSUPPORTED_VERSION(true, NS_XSTREAM, "unsupported-version");
+const QName QN_XSTREAM_XML_NOT_WELL_FORMED(true, NS_XSTREAM, "xml-not-well-formed");
+const QName QN_XSTREAM_TEXT(true, NS_XSTREAM, "text");
+
+const QName QN_TLS_STARTTLS(true, NS_TLS, "starttls");
+const QName QN_TLS_REQUIRED(true, NS_TLS, "required");
+const QName QN_TLS_PROCEED(true, NS_TLS, "proceed");
+const QName QN_TLS_FAILURE(true, NS_TLS, "failure");
+
+const QName QN_SASL_MECHANISMS(true, NS_SASL, "mechanisms");
+const QName QN_SASL_MECHANISM(true, NS_SASL, "mechanism");
+const QName QN_SASL_AUTH(true, NS_SASL, "auth");
+const QName QN_SASL_CHALLENGE(true, NS_SASL, "challenge");
+const QName QN_SASL_RESPONSE(true, NS_SASL, "response");
+const QName QN_SASL_ABORT(true, NS_SASL, "abort");
+const QName QN_SASL_SUCCESS(true, NS_SASL, "success");
+const QName QN_SASL_FAILURE(true, NS_SASL, "failure");
+const QName QN_SASL_ABORTED(true, NS_SASL, "aborted");
+const QName QN_SASL_INCORRECT_ENCODING(true, NS_SASL, "incorrect-encoding");
+const QName QN_SASL_INVALID_AUTHZID(true, NS_SASL, "invalid-authzid");
+const QName QN_SASL_INVALID_MECHANISM(true, NS_SASL, "invalid-mechanism");
+const QName QN_SASL_MECHANISM_TOO_WEAK(true, NS_SASL, "mechanism-too-weak");
+const QName QN_SASL_NOT_AUTHORIZED(true, NS_SASL, "not-authorized");
+const QName QN_SASL_TEMPORARY_AUTH_FAILURE(true, NS_SASL, "temporary-auth-failure");
+
+const QName QN_DIALBACK_RESULT(true, NS_DIALBACK, "result");
+const QName QN_DIALBACK_VERIFY(true, NS_DIALBACK, "verify");
+
+const QName QN_STANZA_BAD_REQUEST(true, NS_STANZA, "bad-request");
+const QName QN_STANZA_CONFLICT(true, NS_STANZA, "conflict");
+const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED(true, NS_STANZA, "feature-not-implemented");
+const QName QN_STANZA_FORBIDDEN(true, NS_STANZA, "forbidden");
+const QName QN_STANZA_GONE(true, NS_STANZA, "gone");
+const QName QN_STANZA_INTERNAL_SERVER_ERROR(true, NS_STANZA, "internal-server-error");
+const QName QN_STANZA_ITEM_NOT_FOUND(true, NS_STANZA, "item-not-found");
+const QName QN_STANZA_JID_MALFORMED(true, NS_STANZA, "jid-malformed");
+const QName QN_STANZA_NOT_ACCEPTABLE(true, NS_STANZA, "not-acceptable");
+const QName QN_STANZA_NOT_ALLOWED(true, NS_STANZA, "not-allowed");
+const QName QN_STANZA_PAYMENT_REQUIRED(true, NS_STANZA, "payment-required");
+const QName QN_STANZA_RECIPIENT_UNAVAILABLE(true, NS_STANZA, "recipient-unavailable");
+const QName QN_STANZA_REDIRECT(true, NS_STANZA, "redirect");
+const QName QN_STANZA_REGISTRATION_REQUIRED(true, NS_STANZA, "registration-required");
+const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND(true, NS_STANZA, "remote-server-not-found");
+const QName QN_STANZA_REMOTE_SERVER_TIMEOUT(true, NS_STANZA, "remote-server-timeout");
+const QName QN_STANZA_RESOURCE_CONSTRAINT(true, NS_STANZA, "resource-constraint");
+const QName QN_STANZA_SERVICE_UNAVAILABLE(true, NS_STANZA, "service-unavailable");
+const QName QN_STANZA_SUBSCRIPTION_REQUIRED(true, NS_STANZA, "subscription-required");
+const QName QN_STANZA_UNDEFINED_CONDITION(true, NS_STANZA, "undefined-condition");
+const QName QN_STANZA_UNEXPECTED_REQUEST(true, NS_STANZA, "unexpected-request");
+const QName QN_STANZA_TEXT(true, NS_STANZA, "text");
+
+const QName QN_BIND_BIND(true, NS_BIND, "bind");
+const QName QN_BIND_RESOURCE(true, NS_BIND, "resource");
+const QName QN_BIND_JID(true, NS_BIND, "jid");
+
+const QName QN_MESSAGE(true, NS_CLIENT, "message");
+const QName QN_BODY(true, NS_CLIENT, "body");
+const QName QN_SUBJECT(true, NS_CLIENT, "subject");
+const QName QN_THREAD(true, NS_CLIENT, "thread");
+const QName QN_PRESENCE(true, NS_CLIENT, "presence");
+const QName QN_SHOW(true, NS_CLIENT, "show");
+const QName QN_STATUS(true, NS_CLIENT, "status");
+const QName QN_LANG(true, NS_CLIENT, "lang");
+const QName QN_PRIORITY(true, NS_CLIENT, "priority");
+const QName QN_IQ(true, NS_CLIENT, "iq");
+const QName QN_ERROR(true, NS_CLIENT, "error");
+
+const QName QN_SERVER_MESSAGE(true, NS_SERVER, "message");
+const QName QN_SERVER_BODY(true, NS_SERVER, "body");
+const QName QN_SERVER_SUBJECT(true, NS_SERVER, "subject");
+const QName QN_SERVER_THREAD(true, NS_SERVER, "thread");
+const QName QN_SERVER_PRESENCE(true, NS_SERVER, "presence");
+const QName QN_SERVER_SHOW(true, NS_SERVER, "show");
+const QName QN_SERVER_STATUS(true, NS_SERVER, "status");
+const QName QN_SERVER_LANG(true, NS_SERVER, "lang");
+const QName QN_SERVER_PRIORITY(true, NS_SERVER, "priority");
+const QName QN_SERVER_IQ(true, NS_SERVER, "iq");
+const QName QN_SERVER_ERROR(true, NS_SERVER, "error");
+
+const QName QN_SESSION_SESSION(true, NS_SESSION, "session");
+
+const QName QN_PRIVACY_QUERY(true, NS_PRIVACY, "query");
+const QName QN_PRIVACY_ACTIVE(true, NS_PRIVACY, "active");
+const QName QN_PRIVACY_DEFAULT(true, NS_PRIVACY, "default");
+const QName QN_PRIVACY_LIST(true, NS_PRIVACY, "list");
+const QName QN_PRIVACY_ITEM(true, NS_PRIVACY, "item");
+const QName QN_PRIVACY_IQ(true, NS_PRIVACY, "iq");
+const QName QN_PRIVACY_MESSAGE(true, NS_PRIVACY, "message");
+const QName QN_PRIVACY_PRESENCE_IN(true, NS_PRIVACY, "presence-in");
+const QName QN_PRIVACY_PRESENCE_OUT(true, NS_PRIVACY, "presence-out");
+
+const QName QN_ROSTER_QUERY(true, NS_ROSTER, "query");
+const QName QN_ROSTER_ITEM(true, NS_ROSTER, "item");
+const QName QN_ROSTER_GROUP(true, NS_ROSTER, "group");
+
+const QName QN_VCARD(true, NS_VCARD, "vCard");
+const QName QN_VCARD_FN(true, NS_VCARD, "FN");
+const QName QN_VCARD_PHOTO(true, NS_VCARD, "PHOTO");
+const QName QN_VCARD_PHOTO_BINVAL(true, NS_VCARD, "BINVAL");
+const QName QN_VCARD_AVATAR_HASH(true, NS_AVATAR_HASH, "hash");
+const QName QN_VCARD_AVATAR_HASH_MODIFIED(true, NS_AVATAR_HASH, "modified");
+
+const buzz::QName QN_NAME(true, STR_EMPTY, "name");
+const buzz::QName QN_AFFILIATION(true, STR_EMPTY, "affiliation");
+const buzz::QName QN_ROLE(true, STR_EMPTY, "role");
+
+#if defined(FEATURE_ENABLE_PSTN)
+const QName QN_VCARD_TEL(true, NS_VCARD, "TEL");
+const QName QN_VCARD_VOICE(true, NS_VCARD, "VOICE");
+const QName QN_VCARD_HOME(true, NS_VCARD, "HOME");
+const QName QN_VCARD_WORK(true, NS_VCARD, "WORK");
+const QName QN_VCARD_CELL(true, NS_VCARD, "CELL");
+const QName QN_VCARD_NUMBER(true, NS_VCARD, "NUMBER");
+#endif
+
+const QName QN_XML_LANG(true, NS_XML, "lang");
+
+const std::string STR_TYPE("type");
+const std::string STR_ID("id");
+const std::string STR_NAME("name");
+const std::string STR_JID("jid");
+const std::string STR_SUBSCRIPTION("subscription");
+const std::string STR_ASK("ask");
+
+const QName QN_ENCODING(true, STR_EMPTY, STR_ENCODING);
+const QName QN_VERSION(true, STR_EMPTY, STR_VERSION);
+const QName QN_TO(true, STR_EMPTY, "to");
+const QName QN_FROM(true, STR_EMPTY, "from");
+const QName QN_TYPE(true, STR_EMPTY, "type");
+const QName QN_ID(true, STR_EMPTY, "id");
+const QName QN_CODE(true, STR_EMPTY, "code");
+
+const QName QN_VALUE(true, STR_EMPTY, "value");
+const QName QN_ACTION(true, STR_EMPTY, "action");
+const QName QN_ORDER(true, STR_EMPTY, "order");
+const QName QN_MECHANISM(true, STR_EMPTY, "mechanism");
+const QName QN_ASK(true, STR_EMPTY, "ask");
+const QName QN_JID(true, STR_EMPTY, "jid");
+const QName QN_SUBSCRIPTION(true, STR_EMPTY, "subscription");
+const QName QN_TITLE1(true, STR_EMPTY, "title1");
+const QName QN_TITLE2(true, STR_EMPTY, "title2");
+const QName QN_SOURCE(true, STR_EMPTY, "source");
+
+const QName QN_XMLNS_CLIENT(true, NS_XMLNS, STR_CLIENT);
+const QName QN_XMLNS_SERVER(true, NS_XMLNS, STR_SERVER);
+const QName QN_XMLNS_STREAM(true, NS_XMLNS, STR_STREAM);
+
+
+
+// Presence
+const std::string STR_SHOW_AWAY("away");
+const std::string STR_SHOW_CHAT("chat");
+const std::string STR_SHOW_DND("dnd");
+const std::string STR_SHOW_XA("xa");
+const std::string STR_SHOW_OFFLINE("offline");
+
+// Subscription
+const std::string STR_SUBSCRIBE("subscribe");
+const std::string STR_SUBSCRIBED("subscribed");
+const std::string STR_UNSUBSCRIBE("unsubscribe");
+const std::string STR_UNSUBSCRIBED("unsubscribed");
+
+// Google Invite
+const std::string NS_GOOGLE_INVITE("google:subscribe");
+const QName QN_INVITATION(true, NS_GOOGLE_INVITE, "invitation");
+const QName QN_INVITE_NAME(true, NS_GOOGLE_INVITE, "name");
+const QName QN_INVITE_SUBJECT(true, NS_GOOGLE_INVITE, "subject");
+const QName QN_INVITE_MESSAGE(true, NS_GOOGLE_INVITE, "body");
+
+// JEP 0030
+const QName QN_NODE(true, STR_EMPTY, "node");
+const QName QN_CATEGORY(true, STR_EMPTY, "category");
+const QName QN_VAR(true, STR_EMPTY, "var");
+const std::string NS_DISCO_INFO("http://jabber.org/protocol/disco#info");
+const std::string NS_DISCO_ITEMS("http://jabber.org/protocol/disco#items");
+const QName QN_DISCO_INFO_QUERY(true, NS_DISCO_INFO, "query");
+const QName QN_DISCO_IDENTITY(true, NS_DISCO_INFO, "identity");
+const QName QN_DISCO_FEATURE(true, NS_DISCO_INFO, "feature");
+
+const QName QN_DISCO_ITEMS_QUERY(true, NS_DISCO_ITEMS, "query");
+const QName QN_DISCO_ITEM(true, NS_DISCO_ITEMS, "item");
+
+const std::string NS_MUC_USER("http://jabber.org/protocol/muc#user");
+const QName QN_MUC_USER_CONTINUE(true, NS_MUC_USER, "continue");
+const QName QN_MUC_USER_X(true, NS_MUC_USER, "x");
+const QName QN_MUC_USER_ITEM(true, NS_MUC_USER, "item");
+const QName QN_MUC_USER_STATUS(true, NS_MUC_USER, "status");
+
+// JEP 0115
+const std::string NS_CAPS("http://jabber.org/protocol/caps");
+const QName QN_CAPS_C(true, NS_CAPS, "c");
+const QName QN_VER(true, STR_EMPTY, "ver");
+const QName QN_EXT(true, STR_EMPTY, "ext");
+
+// JEP 0153
+const std::string kNSVCard("vcard-temp:x:update");
+const QName kQnVCardX(true, kNSVCard, "x");
+const QName kQnVCardPhoto(true, kNSVCard, "photo");
+
+// JEP 0172 User Nickname
+const std::string kNSNickname("http://jabber.org/protocol/nick");
+const QName kQnNickname(true, kNSNickname, "nick");
+
+
+// JEP 0085 chat state
+const std::string NS_CHATSTATE("http://jabber.org/protocol/chatstates");
+const QName QN_CS_ACTIVE(true, NS_CHATSTATE, "active");
+const QName QN_CS_COMPOSING(true, NS_CHATSTATE, "composing");
+const QName QN_CS_PAUSED(true, NS_CHATSTATE, "paused");
+const QName QN_CS_INACTIVE(true, NS_CHATSTATE, "inactive");
+const QName QN_CS_GONE(true, NS_CHATSTATE, "gone");
+
+// JEP 0091 Delayed Delivery
+const std::string kNSDelay("jabber:x:delay");
+const QName kQnDelayX(true, kNSDelay, "x");
+const QName kQnStamp(true, STR_EMPTY, "stamp");
+
+// Google time stamping (higher resolution)
+const std::string kNSTimestamp("google:timestamp");
+const QName kQnTime(true, kNSTimestamp, "time");
+const QName kQnMilliseconds(true, STR_EMPTY, "ms");
+
+
+// Event tracking
+#ifdef FEATURE_ENABLE_TRACKING
+const std::string NS_GOOGLE_EVENT_TRACKING("google:client-usability-testing");
+const QName QN_EVENT_TRACKING(true, NS_GOOGLE_EVENT_TRACKING, "usage-stats");
+const QName QN_EVENT_TRACKING_BRANDID(true, NS_GOOGLE_EVENT_TRACKING, "bid");
+const QName QN_EVENT_TRACKING_EVENT(true, NS_GOOGLE_EVENT_TRACKING, "event");
+const QName QN_EVENT_TRACKING_VARIABLE_KEY(true, STR_EMPTY, "key");
+const QName QN_EVENT_TRACKING_VARIABLE_VALUE(true, STR_EMPTY, "value");
+const QName QN_EVENT_TRACKING_VARIABLE_TIME(true, STR_EMPTY, "time");
+const QName QN_EVENT_TRACKING_EVENT_GROUP(true,
+ NS_GOOGLE_EVENT_TRACKING, "events");
+#endif
+
+
+// Jingle Info
+const std::string NS_JINGLE_INFO("google:jingleinfo");
+const QName QN_JINGLE_INFO_QUERY(true, NS_JINGLE_INFO, "query");
+const QName QN_JINGLE_INFO_STUN(true, NS_JINGLE_INFO, "stun");
+const QName QN_JINGLE_INFO_RELAY(true, NS_JINGLE_INFO, "relay");
+const QName QN_JINGLE_INFO_SERVER(true, NS_JINGLE_INFO, "server");
+const QName QN_JINGLE_INFO_TOKEN(true, NS_JINGLE_INFO, "token");
+const QName QN_JINGLE_INFO_HOST(true, STR_EMPTY, "host");
+const QName QN_JINGLE_INFO_TCP(true, STR_EMPTY, "tcp");
+const QName QN_JINGLE_INFO_UDP(true, STR_EMPTY, "udp");
+const QName QN_JINGLE_INFO_TCPSSL(true, STR_EMPTY, "tcpssl");
+
+// Call Performance Logging
+const std::string NS_GOOGLE_CALLPERF_STATS("google:call-perf-stats");
+const QName QN_CALLPERF_STATS(true, NS_GOOGLE_CALLPERF_STATS, "callPerfStats");
+const QName QN_CALLPERF_SESSIONID(true, STR_EMPTY, "sessionId");
+const QName QN_CALLPERF_LOCALUSER(true, STR_EMPTY, "localUser");
+const QName QN_CALLPERF_REMOTEUSER(true, STR_EMPTY, "remoteUser");
+const QName QN_CALLPERF_STARTTIME(true, STR_EMPTY, "startTime");
+const QName QN_CALLPERF_CALL_LENGTH(true, STR_EMPTY, "callLength");
+const QName QN_CALLPERF_DATAPOINT(true, NS_GOOGLE_CALLPERF_STATS, "dataPoint");
+const QName QN_CALLPERF_DATAPOINT_TIME(true, STR_EMPTY, "timeStamp");
+const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST(true, STR_EMPTY, "fraction_lost");
+const QName QN_CALLPERF_DATAPOINT_CUM_LOST(true, STR_EMPTY, "cum_lost");
+const QName QN_CALLPERF_DATAPOINT_EXT_MAX(true, STR_EMPTY, "ext_max");
+const QName QN_CALLPERF_DATAPOINT_JITTER(true, STR_EMPTY, "jitter");
+const QName QN_CALLPERF_DATAPOINT_RTT(true, STR_EMPTY, "RTT");
+const QName QN_CALLPERF_DATAPOINT_BYTES_R(true, STR_EMPTY, "bytesReceived");
+const QName QN_CALLPERF_DATAPOINT_PACKETS_R(true, STR_EMPTY, "packetsReceived");
+const QName QN_CALLPERF_DATAPOINT_BYTES_S(true, STR_EMPTY, "bytesSent");
+const QName QN_CALLPERF_DATAPOINT_PACKETS_S(true, STR_EMPTY, "packetsSent");
+const QName QN_CALLPERF_CONNECTION(true, NS_GOOGLE_CALLPERF_STATS, "connection");
+const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS(true, STR_EMPTY, "localAddress");
+const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS(true, STR_EMPTY, "remoteAddress");
+
+// Muc invites.
+const QName QN_MUC_USER_INVITE(true, NS_MUC_USER, "invite");
+
+// Multiway audio/video.
+const std::string NS_GOOGLE_MUC_USER("google:muc#user");
+const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA(true, NS_GOOGLE_MUC_USER, "available-media");
+const QName QN_GOOGLE_MUC_USER_ENTRY(true, NS_GOOGLE_MUC_USER, "entry");
+const QName QN_GOOGLE_MUC_USER_MEDIA(true, NS_GOOGLE_MUC_USER, "media");
+const QName QN_GOOGLE_MUC_USER_TYPE(true, NS_GOOGLE_MUC_USER, "type");
+const QName QN_GOOGLE_MUC_USER_SRC_ID(true, NS_GOOGLE_MUC_USER, "src-id");
+const QName QN_GOOGLE_MUC_USER_STATUS(true, NS_GOOGLE_MUC_USER, "status");
+const QName QN_LABEL(true, STR_EMPTY, "label");
+
+}
diff --git a/talk/xmpp/constants.h b/talk/xmpp/constants.h
new file mode 100644
index 0000000..1a626b4
--- /dev/null
+++ b/talk/xmpp/constants.h
@@ -0,0 +1,436 @@
+/*
+ * 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_XMPP_CONSTANTS_H_
+#define TALK_XMPP_CONSTANTS_H_
+
+#include <string>
+#include "talk/xmllite/qname.h"
+#include "talk/xmpp/jid.h"
+
+
+#define NS_CLIENT Constants::ns_client()
+#define NS_SERVER Constants::ns_server()
+#define NS_STREAM Constants::ns_stream()
+#define NS_XSTREAM Constants::ns_xstream()
+#define NS_TLS Constants::ns_tls()
+#define NS_SASL Constants::ns_sasl()
+#define NS_BIND Constants::ns_bind()
+#define NS_DIALBACK Constants::ns_dialback()
+#define NS_SESSION Constants::ns_session()
+#define NS_STANZA Constants::ns_stanza()
+#define NS_PRIVACY Constants::ns_privacy()
+#define NS_ROSTER Constants::ns_roster()
+#define NS_VCARD Constants::ns_vcard()
+#define NS_AVATAR_HASH Constants::ns_avatar_hash()
+#define NS_VCARD_UPDATE Constants::ns_vcard_update()
+#define STR_CLIENT Constants::str_client()
+#define STR_SERVER Constants::str_server()
+#define STR_STREAM Constants::str_stream()
+
+
+namespace buzz {
+
+extern const Jid JID_EMPTY;
+
+class Constants {
+ public:
+ static const std::string & ns_client();
+ static const std::string & ns_server();
+ static const std::string & ns_stream();
+ static const std::string & ns_xstream();
+ static const std::string & ns_tls();
+ static const std::string & ns_sasl();
+ static const std::string & ns_bind();
+ static const std::string & ns_dialback();
+ static const std::string & ns_session();
+ static const std::string & ns_stanza();
+ static const std::string & ns_privacy();
+ static const std::string & ns_roster();
+ static const std::string & ns_vcard();
+ static const std::string & ns_avatar_hash();
+ static const std::string & ns_vcard_update();
+
+ static const std::string & str_client();
+ static const std::string & str_server();
+ static const std::string & str_stream();
+};
+
+extern const std::string STR_GET;
+extern const std::string STR_SET;
+extern const std::string STR_RESULT;
+extern const std::string STR_ERROR;
+
+
+extern const std::string STR_FROM;
+extern const std::string STR_TO;
+extern const std::string STR_BOTH;
+extern const std::string STR_REMOVE;
+
+extern const std::string STR_MESSAGE;
+extern const std::string STR_BODY;
+extern const std::string STR_PRESENCE;
+extern const std::string STR_STATUS;
+extern const std::string STR_SHOW;
+extern const std::string STR_PRIOIRTY;
+extern const std::string STR_IQ;
+
+extern const std::string STR_TYPE;
+extern const std::string STR_NAME;
+extern const std::string STR_ID;
+extern const std::string STR_JID;
+extern const std::string STR_SUBSCRIPTION;
+extern const std::string STR_ASK;
+extern const std::string STR_X;
+extern const std::string STR_GOOGLE_COM;
+extern const std::string STR_GMAIL_COM;
+extern const std::string STR_GOOGLEMAIL_COM;
+extern const std::string STR_DEFAULT_DOMAIN;
+extern const std::string STR_TALK_GOOGLE_COM;
+extern const std::string STR_TALKX_L_GOOGLE_COM;
+
+#ifdef FEATURE_ENABLE_VOICEMAIL
+extern const std::string STR_VOICEMAIL;
+extern const std::string STR_OUTGOINGVOICEMAIL;
+#endif
+
+extern const std::string STR_UNAVAILABLE;
+
+extern const QName QN_STREAM_STREAM;
+extern const QName QN_STREAM_FEATURES;
+extern const QName QN_STREAM_ERROR;
+
+extern const QName QN_XSTREAM_BAD_FORMAT;
+extern const QName QN_XSTREAM_BAD_NAMESPACE_PREFIX;
+extern const QName QN_XSTREAM_CONFLICT;
+extern const QName QN_XSTREAM_CONNECTION_TIMEOUT;
+extern const QName QN_XSTREAM_HOST_GONE;
+extern const QName QN_XSTREAM_HOST_UNKNOWN;
+extern const QName QN_XSTREAM_IMPROPER_ADDRESSIING;
+extern const QName QN_XSTREAM_INTERNAL_SERVER_ERROR;
+extern const QName QN_XSTREAM_INVALID_FROM;
+extern const QName QN_XSTREAM_INVALID_ID;
+extern const QName QN_XSTREAM_INVALID_NAMESPACE;
+extern const QName QN_XSTREAM_INVALID_XML;
+extern const QName QN_XSTREAM_NOT_AUTHORIZED;
+extern const QName QN_XSTREAM_POLICY_VIOLATION;
+extern const QName QN_XSTREAM_REMOTE_CONNECTION_FAILED;
+extern const QName QN_XSTREAM_RESOURCE_CONSTRAINT;
+extern const QName QN_XSTREAM_RESTRICTED_XML;
+extern const QName QN_XSTREAM_SEE_OTHER_HOST;
+extern const QName QN_XSTREAM_SYSTEM_SHUTDOWN;
+extern const QName QN_XSTREAM_UNDEFINED_CONDITION;
+extern const QName QN_XSTREAM_UNSUPPORTED_ENCODING;
+extern const QName QN_XSTREAM_UNSUPPORTED_STANZA_TYPE;
+extern const QName QN_XSTREAM_UNSUPPORTED_VERSION;
+extern const QName QN_XSTREAM_XML_NOT_WELL_FORMED;
+extern const QName QN_XSTREAM_TEXT;
+
+extern const QName QN_TLS_STARTTLS;
+extern const QName QN_TLS_REQUIRED;
+extern const QName QN_TLS_PROCEED;
+extern const QName QN_TLS_FAILURE;
+
+extern const QName QN_SASL_MECHANISMS;
+extern const QName QN_SASL_MECHANISM;
+extern const QName QN_SASL_AUTH;
+extern const QName QN_SASL_CHALLENGE;
+extern const QName QN_SASL_RESPONSE;
+extern const QName QN_SASL_ABORT;
+extern const QName QN_SASL_SUCCESS;
+extern const QName QN_SASL_FAILURE;
+extern const QName QN_SASL_ABORTED;
+extern const QName QN_SASL_INCORRECT_ENCODING;
+extern const QName QN_SASL_INVALID_AUTHZID;
+extern const QName QN_SASL_INVALID_MECHANISM;
+extern const QName QN_SASL_MECHANISM_TOO_WEAK;
+extern const QName QN_SASL_NOT_AUTHORIZED;
+extern const QName QN_SASL_TEMPORARY_AUTH_FAILURE;
+
+extern const QName QN_DIALBACK_RESULT;
+extern const QName QN_DIALBACK_VERIFY;
+
+extern const QName QN_STANZA_BAD_REQUEST;
+extern const QName QN_STANZA_CONFLICT;
+extern const QName QN_STANZA_FEATURE_NOT_IMPLEMENTED;
+extern const QName QN_STANZA_FORBIDDEN;
+extern const QName QN_STANZA_GONE;
+extern const QName QN_STANZA_INTERNAL_SERVER_ERROR;
+extern const QName QN_STANZA_ITEM_NOT_FOUND;
+extern const QName QN_STANZA_JID_MALFORMED;
+extern const QName QN_STANZA_NOT_ACCEPTABLE;
+extern const QName QN_STANZA_NOT_ALLOWED;
+extern const QName QN_STANZA_PAYMENT_REQUIRED;
+extern const QName QN_STANZA_RECIPIENT_UNAVAILABLE;
+extern const QName QN_STANZA_REDIRECT;
+extern const QName QN_STANZA_REGISTRATION_REQUIRED;
+extern const QName QN_STANZA_REMOTE_SERVER_NOT_FOUND;
+extern const QName QN_STANZA_REMOTE_SERVER_TIMEOUT;
+extern const QName QN_STANZA_RESOURCE_CONSTRAINT;
+extern const QName QN_STANZA_SERVICE_UNAVAILABLE;
+extern const QName QN_STANZA_SUBSCRIPTION_REQUIRED;
+extern const QName QN_STANZA_UNDEFINED_CONDITION;
+extern const QName QN_STANZA_UNEXPECTED_REQUEST;
+extern const QName QN_STANZA_TEXT;
+
+extern const QName QN_BIND_BIND;
+extern const QName QN_BIND_RESOURCE;
+extern const QName QN_BIND_JID;
+
+extern const QName QN_MESSAGE;
+extern const QName QN_BODY;
+extern const QName QN_SUBJECT;
+extern const QName QN_THREAD;
+extern const QName QN_PRESENCE;
+extern const QName QN_SHOW;
+extern const QName QN_STATUS;
+extern const QName QN_LANG;
+extern const QName QN_PRIORITY;
+extern const QName QN_IQ;
+extern const QName QN_ERROR;
+
+extern const QName QN_SERVER_MESSAGE;
+extern const QName QN_SERVER_BODY;
+extern const QName QN_SERVER_SUBJECT;
+extern const QName QN_SERVER_THREAD;
+extern const QName QN_SERVER_PRESENCE;
+extern const QName QN_SERVER_SHOW;
+extern const QName QN_SERVER_STATUS;
+extern const QName QN_SERVER_LANG;
+extern const QName QN_SERVER_PRIORITY;
+extern const QName QN_SERVER_IQ;
+extern const QName QN_SERVER_ERROR;
+
+extern const QName QN_SESSION_SESSION;
+
+extern const QName QN_PRIVACY_QUERY;
+extern const QName QN_PRIVACY_ACTIVE;
+extern const QName QN_PRIVACY_DEFAULT;
+extern const QName QN_PRIVACY_LIST;
+extern const QName QN_PRIVACY_ITEM;
+extern const QName QN_PRIVACY_IQ;
+extern const QName QN_PRIVACY_MESSAGE;
+extern const QName QN_PRIVACY_PRESENCE_IN;
+extern const QName QN_PRIVACY_PRESENCE_OUT;
+
+extern const QName QN_ROSTER_QUERY;
+extern const QName QN_ROSTER_ITEM;
+extern const QName QN_ROSTER_GROUP;
+
+extern const QName QN_VCARD;
+extern const QName QN_VCARD_FN;
+extern const QName QN_VCARD_PHOTO;
+extern const QName QN_VCARD_PHOTO_BINVAL;
+extern const QName QN_VCARD_AVATAR_HASH;
+extern const QName QN_VCARD_AVATAR_HASH_MODIFIED;
+
+#if defined(FEATURE_ENABLE_PSTN)
+extern const QName QN_VCARD_TEL;
+extern const QName QN_VCARD_VOICE;
+extern const QName QN_VCARD_HOME;
+extern const QName QN_VCARD_WORK;
+extern const QName QN_VCARD_CELL;
+extern const QName QN_VCARD_NUMBER;
+#endif
+
+#if defined(FEATURE_ENABLE_RICHPROFILES)
+extern const QName QN_USER_PROFILE_QUERY;
+extern const QName QN_USER_PROFILE_URL;
+
+extern const QName QN_ATOM_FEED;
+extern const QName QN_ATOM_ENTRY;
+extern const QName QN_ATOM_TITLE;
+extern const QName QN_ATOM_ID;
+extern const QName QN_ATOM_MODIFIED;
+extern const QName QN_ATOM_IMAGE;
+extern const QName QN_ATOM_LINK;
+extern const QName QN_ATOM_HREF;
+#endif
+
+extern const QName QN_XML_LANG;
+
+extern const QName QN_ENCODING;
+extern const QName QN_VERSION;
+extern const QName QN_TO;
+extern const QName QN_FROM;
+extern const QName QN_TYPE;
+extern const QName QN_ID;
+extern const QName QN_CODE;
+extern const QName QN_NAME;
+extern const QName QN_VALUE;
+extern const QName QN_ACTION;
+extern const QName QN_ORDER;
+extern const QName QN_MECHANISM;
+extern const QName QN_ASK;
+extern const QName QN_JID;
+extern const QName QN_SUBSCRIPTION;
+extern const QName QN_TITLE1;
+extern const QName QN_TITLE2;
+extern const QName QN_AFFILIATION;
+extern const QName QN_ROLE;
+
+
+extern const QName QN_XMLNS_CLIENT;
+extern const QName QN_XMLNS_SERVER;
+extern const QName QN_XMLNS_STREAM;
+
+// Presence
+extern const std::string STR_SHOW_AWAY;
+extern const std::string STR_SHOW_CHAT;
+extern const std::string STR_SHOW_DND;
+extern const std::string STR_SHOW_XA;
+extern const std::string STR_SHOW_OFFLINE;
+
+// Subscription
+extern const std::string STR_SUBSCRIBE;
+extern const std::string STR_SUBSCRIBED;
+extern const std::string STR_UNSUBSCRIBE;
+extern const std::string STR_UNSUBSCRIBED;
+
+// Google Invite
+extern const std::string NS_GOOGLE_SUBSCRIBE;
+extern const QName QN_INVITATION;
+extern const QName QN_INVITE_NAME;
+extern const QName QN_INVITE_SUBJECT;
+extern const QName QN_INVITE_MESSAGE;
+
+
+// JEP 0030
+extern const QName QN_NODE;
+extern const QName QN_CATEGORY;
+extern const QName QN_VAR;
+extern const std::string NS_DISCO_INFO;
+extern const std::string NS_DISCO_ITEMS;
+
+extern const QName QN_DISCO_INFO_QUERY;
+extern const QName QN_DISCO_IDENTITY;
+extern const QName QN_DISCO_FEATURE;
+
+extern const QName QN_DISCO_ITEMS_QUERY;
+extern const QName QN_DISCO_ITEM;
+
+
+// JEP 0045
+extern const std::string NS_MUC;
+extern const QName QN_MUC_X;
+extern const QName QN_MUC_ITEM;
+extern const QName QN_MUC_AFFILIATION;
+extern const QName QN_MUC_ROLE;
+extern const std::string STR_AFFILIATION_NONE;
+extern const std::string STR_ROLE_PARTICIPANT;
+extern const std::string NS_MUC_USER;
+extern const QName QN_MUC_USER_CONTINUE;
+extern const QName QN_MUC_USER_X;
+extern const QName QN_MUC_USER_ITEM;
+extern const QName QN_MUC_USER_STATUS;
+
+
+// JEP 0115
+extern const std::string NS_CAPS;
+extern const QName QN_CAPS_C;
+extern const QName QN_VER;
+extern const QName QN_EXT;
+
+
+// Avatar - JEP 0153
+extern const std::string kNSVCard;
+extern const QName kQnVCardX;
+extern const QName kQnVCardPhoto;
+
+// JEP 0172 User Nickname
+extern const std::string kNSNickname;
+extern const QName kQnNickname;
+
+
+// JEP 0085 chat state
+extern const std::string NS_CHATSTATE;
+extern const QName QN_CS_ACTIVE;
+extern const QName QN_CS_COMPOSING;
+extern const QName QN_CS_PAUSED;
+extern const QName QN_CS_INACTIVE;
+extern const QName QN_CS_GONE;
+
+// JEP 0091 Delayed Delivery
+extern const std::string kNSDelay;
+extern const QName kQnDelayX;
+extern const QName kQnStamp;
+
+// Google time stamping (higher resolution)
+extern const std::string kNSTimestamp;
+extern const QName kQnTime;
+extern const QName kQnMilliseconds;
+
+
+extern const std::string NS_JINGLE_INFO;
+extern const QName QN_JINGLE_INFO_QUERY;
+extern const QName QN_JINGLE_INFO_STUN;
+extern const QName QN_JINGLE_INFO_RELAY;
+extern const QName QN_JINGLE_INFO_SERVER;
+extern const QName QN_JINGLE_INFO_TOKEN;
+extern const QName QN_JINGLE_INFO_HOST;
+extern const QName QN_JINGLE_INFO_TCP;
+extern const QName QN_JINGLE_INFO_UDP;
+extern const QName QN_JINGLE_INFO_TCPSSL;
+
+extern const std::string NS_GOOGLE_CALLPERF_STATS;
+extern const QName QN_CALLPERF_STATS;
+extern const QName QN_CALLPERF_SESSIONID;
+extern const QName QN_CALLPERF_LOCALUSER;
+extern const QName QN_CALLPERF_REMOTEUSER;
+extern const QName QN_CALLPERF_STARTTIME;
+extern const QName QN_CALLPERF_CALL_LENGTH;
+extern const QName QN_CALLPERF_DATAPOINT;
+extern const QName QN_CALLPERF_DATAPOINT_TIME;
+extern const QName QN_CALLPERF_DATAPOINT_FRACTION_LOST;
+extern const QName QN_CALLPERF_DATAPOINT_CUM_LOST;
+extern const QName QN_CALLPERF_DATAPOINT_EXT_MAX;
+extern const QName QN_CALLPERF_DATAPOINT_JITTER;
+extern const QName QN_CALLPERF_DATAPOINT_RTT;
+extern const QName QN_CALLPERF_DATAPOINT_BYTES_R;
+extern const QName QN_CALLPERF_DATAPOINT_PACKETS_R;
+extern const QName QN_CALLPERF_DATAPOINT_BYTES_S;
+extern const QName QN_CALLPERF_DATAPOINT_PACKETS_S;
+extern const QName QN_CALLPERF_CONNECTION;
+extern const QName QN_CALLPERF_CONNECTION_LOCAL_ADDRESS;
+extern const QName QN_CALLPERF_CONNECTION_REMOTE_ADDRESS;
+
+// Muc invites.
+extern const QName QN_MUC_USER_INVITE;
+
+// Multiway audio/video.
+extern const std::string NS_GOOGLE_MUC_USER;
+extern const QName QN_GOOGLE_MUC_USER_AVAILABLE_MEDIA;
+extern const QName QN_GOOGLE_MUC_USER_ENTRY;
+extern const QName QN_GOOGLE_MUC_USER_MEDIA;
+extern const QName QN_GOOGLE_MUC_USER_TYPE;
+extern const QName QN_GOOGLE_MUC_USER_SRC_ID;
+extern const QName QN_GOOGLE_MUC_USER_STATUS;
+extern const QName QN_LABEL;
+
+} // namespace buzz
+
+#endif // TALK_XMPP_CONSTANTS_H_
diff --git a/talk/xmpp/jid.cc b/talk/xmpp/jid.cc
new file mode 100644
index 0000000..01a025f
--- /dev/null
+++ b/talk/xmpp/jid.cc
@@ -0,0 +1,503 @@
+/*
+ * 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 "talk/xmpp/jid.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+#include <string>
+
+#include "talk/base/common.h"
+#include "talk/base/logging.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+Jid::Jid() : data_(NULL) {
+}
+
+Jid::Jid(bool is_special, const std::string & special) {
+ data_ = is_special ? new Data(special, STR_EMPTY, STR_EMPTY) : NULL;
+}
+
+Jid::Jid(const std::string & jid_string) {
+ if (jid_string == STR_EMPTY) {
+ data_ = NULL;
+ return;
+ }
+
+ // First find the slash and slice of that part
+ size_t slash = jid_string.find('/');
+ std::string resource_name = (slash == std::string::npos ? STR_EMPTY :
+ jid_string.substr(slash + 1));
+
+ // Now look for the node
+ std::string node_name;
+ size_t at = jid_string.find('@');
+ size_t domain_begin;
+ if (at < slash && at != std::string::npos) {
+ node_name = jid_string.substr(0, at);
+ domain_begin = at + 1;
+ } else {
+ domain_begin = 0;
+ }
+
+ // Now take what is left as the domain
+ size_t domain_length =
+ ( slash == std::string::npos
+ ? jid_string.length() - domain_begin
+ : slash - domain_begin);
+
+ // avoid allocating these constants repeatedly
+ std::string domain_name;
+
+ if (domain_length == 9 && jid_string.find("gmail.com", domain_begin) == domain_begin) {
+ domain_name = STR_GMAIL_COM;
+ }
+ else if (domain_length == 14 && jid_string.find("googlemail.com", domain_begin) == domain_begin) {
+ domain_name = STR_GOOGLEMAIL_COM;
+ }
+ else if (domain_length == 10 && jid_string.find("google.com", domain_begin) == domain_begin) {
+ domain_name = STR_GOOGLE_COM;
+ }
+ else {
+ domain_name = jid_string.substr(domain_begin, domain_length);
+ }
+
+ // If the domain is empty we have a non-valid jid and we should empty
+ // everything else out
+ if (domain_name.empty()) {
+ data_ = NULL;
+ return;
+ }
+
+ bool valid_node;
+ std::string validated_node = prepNode(node_name,
+ node_name.begin(), node_name.end(), &valid_node);
+ bool valid_domain;
+ std::string validated_domain = prepDomain(domain_name,
+ domain_name.begin(), domain_name.end(), &valid_domain);
+ bool valid_resource;
+ std::string validated_resource = prepResource(resource_name,
+ resource_name.begin(), resource_name.end(), &valid_resource);
+
+ if (!valid_node || !valid_domain || !valid_resource) {
+ data_ = NULL;
+ return;
+ }
+
+ data_ = new Data(validated_node, validated_domain, validated_resource);
+}
+
+Jid::Jid(const std::string & node_name,
+ const std::string & domain_name,
+ const std::string & resource_name) {
+ if (domain_name.empty()) {
+ data_ = NULL;
+ return;
+ }
+
+ bool valid_node;
+ std::string validated_node = prepNode(node_name,
+ node_name.begin(), node_name.end(), &valid_node);
+ bool valid_domain;
+ std::string validated_domain = prepDomain(domain_name,
+ domain_name.begin(), domain_name.end(), &valid_domain);
+ bool valid_resource;
+ std::string validated_resource = prepResource(resource_name,
+ resource_name.begin(), resource_name.end(), &valid_resource);
+
+ if (!valid_node || !valid_domain || !valid_resource) {
+ data_ = NULL;
+ return;
+ }
+
+ data_ = new Data(validated_node, validated_domain, validated_resource);
+}
+
+std::string Jid::Str() const {
+ if (!IsValid())
+ return STR_EMPTY;
+
+ std::string ret;
+
+ if (!data_->node_name_.empty())
+ ret = data_->node_name_ + "@";
+
+ ASSERT(data_->domain_name_ != STR_EMPTY);
+ ret += data_->domain_name_;
+
+ if (!data_->resource_name_.empty())
+ ret += "/" + data_->resource_name_;
+
+ return ret;
+}
+
+bool
+Jid::IsValid() const {
+ return data_ != NULL && !data_->domain_name_.empty();
+}
+
+bool
+Jid::IsBare() const {
+ if (Compare(JID_EMPTY) == 0) {
+ LOG(LS_VERBOSE) << "Warning: Calling IsBare() on the empty jid";
+ return true;
+ }
+ return IsValid() &&
+ data_->resource_name_.empty();
+}
+
+bool
+Jid::IsFull() const {
+ return IsValid() &&
+ !data_->resource_name_.empty();
+}
+
+Jid
+Jid::BareJid() const {
+ if (!IsValid())
+ return Jid();
+ if (!IsFull())
+ return *this;
+ return Jid(data_->node_name_, data_->domain_name_, STR_EMPTY);
+}
+
+#if 0
+void
+Jid::set_node(const std::string & node_name) {
+ data_->node_name_ = node_name;
+}
+void
+Jid::set_domain(const std::string & domain_name) {
+ data_->domain_name_ = domain_name;
+}
+void
+Jid::set_resource(const std::string & res_name) {
+ data_->resource_name_ = res_name;
+}
+#endif
+
+bool
+Jid::BareEquals(const Jid & other) const {
+ return (other.data_ == data_ ||
+ (data_ != NULL &&
+ other.data_ != NULL &&
+ other.data_->node_name_ == data_->node_name_ &&
+ other.data_->domain_name_ == data_->domain_name_));
+}
+
+bool
+Jid::operator==(const Jid & other) const {
+ return (other.data_ == data_ ||
+ (data_ != NULL &&
+ other.data_ != NULL &&
+ other.data_->node_name_ == data_->node_name_ &&
+ other.data_->domain_name_ == data_->domain_name_ &&
+ other.data_->resource_name_ == data_->resource_name_));
+}
+
+int
+Jid::Compare(const Jid & other) const {
+ if (other.data_ == data_)
+ return 0;
+ if (data_ == NULL)
+ return -1;
+ if (other.data_ == NULL)
+ return 1;
+
+ int compare_result;
+ compare_result = data_->node_name_.compare(other.data_->node_name_);
+ if (0 != compare_result)
+ return compare_result;
+ compare_result = data_->domain_name_.compare(other.data_->domain_name_);
+ if (0 != compare_result)
+ return compare_result;
+ compare_result = data_->resource_name_.compare(other.data_->resource_name_);
+ return compare_result;
+}
+
+uint32 Jid::ComputeLameHash() const {
+ uint32 hash = 0;
+ // Hash the node portion
+ {
+ const std::string &str = node();
+ for (int i = 0; i < static_cast<int>(str.size()); ++i) {
+ hash = ((hash << 2) + hash) + str[i];
+ }
+ }
+
+ // Hash the domain portion
+ {
+ const std::string &str = domain();
+ for (int i = 0; i < static_cast<int>(str.size()); ++i)
+ hash = ((hash << 2) + hash) + str[i];
+ }
+
+ // Hash the resource portion
+ {
+ const std::string &str = resource();
+ for (int i = 0; i < static_cast<int>(str.size()); ++i)
+ hash = ((hash << 2) + hash) + str[i];
+ }
+
+ return hash;
+}
+
+// --- JID parsing code: ---
+
+// Checks and normalizes the node part of a JID.
+std::string
+Jid::prepNode(const std::string str, std::string::const_iterator start,
+ std::string::const_iterator end, bool *valid) {
+ *valid = false;
+ std::string result;
+
+ for (std::string::const_iterator i = start; i < end; i++) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ result += prepNodeAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement the correct stringprep protocol for these
+ result += tolower(ch);
+ }
+ if (!char_valid) {
+ return STR_EMPTY;
+ }
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a node.
+char
+Jid::prepNodeAscii(char ch, bool *valid) {
+ *valid = true;
+ switch (ch) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+ case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+ case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+ case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ return (char)(ch + ('a' - 'A'));
+
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case ' ': case '&': case '/': case ':': case '<': case '>': case '@':
+ case '\"': case '\'':
+ case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+
+// Checks and normalizes the resource part of a JID.
+std::string
+Jid::prepResource(const std::string str, std::string::const_iterator start,
+ std::string::const_iterator end, bool *valid) {
+ *valid = false;
+ std::string result;
+
+ for (std::string::const_iterator i = start; i < end; i++) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ result += prepResourceAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement the correct stringprep protocol for these
+ result += ch;
+ }
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+// Returns the appropriate mapping for an ASCII character in a resource.
+char
+Jid::prepResourceAscii(char ch, bool *valid) {
+ *valid = true;
+ switch (ch) {
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+// Checks and normalizes the domain part of a JID.
+std::string
+Jid::prepDomain(const std::string str, std::string::const_iterator start,
+ std::string::const_iterator end, bool *valid) {
+ *valid = false;
+ std::string result;
+
+ // TODO: if the domain contains a ':', then we should parse it
+ // as an IPv6 address rather than giving an error about illegal domain.
+ prepDomain(str, start, end, &result, valid);
+ if (!*valid) {
+ return STR_EMPTY;
+ }
+
+ if (result.length() > 1023) {
+ return STR_EMPTY;
+ }
+ *valid = true;
+ return result;
+}
+
+
+// Checks and normalizes an IDNA domain.
+void
+Jid::prepDomain(const std::string str, std::string::const_iterator start,
+ std::string::const_iterator end, std::string *buf, bool *valid) {
+ *valid = false;
+ std::string::const_iterator last = start;
+ for (std::string::const_iterator i = start; i < end; i++) {
+ bool label_valid = true;
+ char ch = *i;
+ switch (ch) {
+ case 0x002E:
+#if 0 // FIX: This isn't UTF-8-aware.
+ case 0x3002:
+ case 0xFF0E:
+ case 0xFF61:
+#endif
+ prepDomainLabel(str, last, i, buf, &label_valid);
+ *buf += '.';
+ last = i + 1;
+ break;
+ }
+ if (!label_valid) {
+ return;
+ }
+ }
+ prepDomainLabel(str, last, end, buf, valid);
+}
+
+// Checks and normalizes a domain label.
+void
+Jid::prepDomainLabel(const std::string str, std::string::const_iterator start,
+ std::string::const_iterator end, std::string *buf, bool *valid) {
+ *valid = false;
+
+ int startLen = buf->length();
+ for (std::string::const_iterator i = start; i < end; i++) {
+ bool char_valid = true;
+ unsigned char ch = *i;
+ if (ch <= 0x7F) {
+ *buf += prepDomainLabelAscii(ch, &char_valid);
+ }
+ else {
+ // TODO: implement ToASCII for these
+ *buf += ch;
+ }
+ if (!char_valid) {
+ return;
+ }
+ }
+
+ int count = buf->length() - startLen;
+ if (count == 0) {
+ return;
+ }
+ else if (count > 63) {
+ return;
+ }
+
+ // Is this check needed? See comment in prepDomainLabelAscii.
+ if ((*buf)[startLen] == '-') {
+ return;
+ }
+ if ((*buf)[buf->length() - 1] == '-') {
+ return;
+ }
+ *valid = true;
+}
+
+
+// Returns the appropriate mapping for an ASCII character in a domain label.
+char
+Jid::prepDomainLabelAscii(char ch, bool *valid) {
+ *valid = true;
+ // TODO: A literal reading of the spec seems to say that we do
+ // not need to check for these illegal characters (an "internationalized
+ // domain label" runs ToASCII with UseSTD3... set to false). But that
+ // can't be right. We should at least be checking that there are no '/'
+ // or '@' characters in the domain. Perhaps we should see what others
+ // do in this case.
+
+ switch (ch) {
+ case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G':
+ case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N':
+ case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U':
+ case 'V': case 'W': case 'X': case 'Y': case 'Z':
+ return (char)(ch + ('a' - 'A'));
+
+ case 0x00: case 0x01: case 0x02: case 0x03: case 0x04: case 0x05:
+ case 0x06: case 0x07: case 0x08: case 0x09: case 0x0A: case 0x0B:
+ case 0x0C: case 0x0D: case 0x0E: case 0x0F: case 0x10: case 0x11:
+ case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
+ case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D:
+ case 0x1E: case 0x1F: case 0x20: case 0x21: case 0x22: case 0x23:
+ case 0x24: case 0x25: case 0x26: case 0x27: case 0x28: case 0x29:
+ case 0x2A: case 0x2B: case 0x2C: case 0x2E: case 0x2F: case 0x3A:
+ case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F: case 0x40:
+ case 0x5B: case 0x5C: case 0x5D: case 0x5E: case 0x5F: case 0x60:
+ case 0x7B: case 0x7C: case 0x7D: case 0x7E: case 0x7F:
+ *valid = false;
+ return 0;
+
+ default:
+ return ch;
+ }
+}
+
+}
diff --git a/talk/xmpp/jid.h b/talk/xmpp/jid.h
new file mode 100644
index 0000000..6831bda
--- /dev/null
+++ b/talk/xmpp/jid.h
@@ -0,0 +1,148 @@
+/*
+ * 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 _jid_h_
+#define _jid_h_
+
+#include <string>
+#include "talk/base/basictypes.h"
+#include "talk/xmllite/xmlconstants.h"
+
+namespace buzz {
+
+//! The Jid class encapsulates and provides parsing help for Jids
+//! A Jid consists of three parts. The node, the domain and the resource.
+//!
+//! node@domain/resource
+//!
+//! The node and resource are both optional. A valid jid is defined to have
+//! a domain. A bare jid is defined to not have a resource and a full jid
+//! *does* have a resource.
+class Jid {
+public:
+ explicit Jid();
+ explicit Jid(const std::string & jid_string);
+ explicit Jid(const std::string & node_name,
+ const std::string & domain_name,
+ const std::string & resource_name);
+ explicit Jid(bool special, const std::string & special_string);
+ Jid(const Jid & jid) : data_(jid.data_) {
+ if (data_ != NULL) {
+ data_->AddRef();
+ }
+ }
+ Jid & operator=(const Jid & jid) {
+ if (jid.data_ != NULL) {
+ jid.data_->AddRef();
+ }
+ if (data_ != NULL) {
+ data_->Release();
+ }
+ data_ = jid.data_;
+ return *this;
+ }
+ ~Jid() {
+ if (data_ != NULL) {
+ data_->Release();
+ }
+ }
+
+
+ const std::string & node() const { return !data_ ? STR_EMPTY : data_->node_name_; }
+ // void set_node(const std::string & node_name);
+ const std::string & domain() const { return !data_ ? STR_EMPTY : data_->domain_name_; }
+ // void set_domain(const std::string & domain_name);
+ const std::string & resource() const { return !data_ ? STR_EMPTY : data_->resource_name_; }
+ // void set_resource(const std::string & res_name);
+
+ std::string Str() const;
+ Jid BareJid() const;
+
+ bool IsValid() const;
+ bool IsBare() const;
+ bool IsFull() const;
+
+ bool BareEquals(const Jid & other) const;
+
+ bool operator==(const Jid & other) const;
+ bool operator!=(const Jid & other) const { return !operator==(other); }
+
+ bool operator<(const Jid & other) const { return Compare(other) < 0; };
+ bool operator>(const Jid & other) const { return Compare(other) > 0; };
+
+ int Compare(const Jid & other) const;
+
+ // A quick and dirty hash. Don't count on this producing a great
+ // distribution.
+ uint32 ComputeLameHash() const;
+
+private:
+
+ static std::string prepNode(const std::string str,
+ std::string::const_iterator start, std::string::const_iterator end,
+ bool *valid);
+ static char prepNodeAscii(char ch, bool *valid);
+ static std::string prepResource(const std::string str,
+ std::string::const_iterator start, std::string::const_iterator end,
+ bool *valid);
+ static char prepResourceAscii(char ch, bool *valid);
+ static std::string prepDomain(const std::string str,
+ std::string::const_iterator start, std::string::const_iterator end,
+ bool *valid);
+ static void prepDomain(const std::string str,
+ std::string::const_iterator start, std::string::const_iterator end,
+ std::string *buf, bool *valid);
+ static void prepDomainLabel(const std::string str,
+ std::string::const_iterator start, std::string::const_iterator end,
+ std::string *buf, bool *valid);
+ static char prepDomainLabelAscii(char ch, bool *valid);
+
+ class Data {
+ public:
+ Data() : refcount_(1) {}
+ Data(const std::string & node, const std::string &domain, const std::string & resource) :
+ node_name_(node),
+ domain_name_(domain),
+ resource_name_(resource),
+ refcount_(1) {}
+ const std::string node_name_;
+ const std::string domain_name_;
+ const std::string resource_name_;
+
+ void AddRef() { refcount_++; }
+ void Release() { if (!--refcount_) delete this; }
+ private:
+ int refcount_;
+ };
+
+ Data * data_;
+};
+
+}
+
+
+
+#endif
diff --git a/talk/xmpp/plainsaslhandler.h b/talk/xmpp/plainsaslhandler.h
new file mode 100644
index 0000000..e7d44b9
--- /dev/null
+++ b/talk/xmpp/plainsaslhandler.h
@@ -0,0 +1,80 @@
+/*
+ * 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 _PLAINSASLHANDLER_H_
+#define _PLAINSASLHANDLER_H_
+
+#include "talk/xmpp/saslhandler.h"
+#include <algorithm>
+
+namespace buzz {
+
+class PlainSaslHandler : public SaslHandler {
+public:
+ PlainSaslHandler(const Jid & jid, const talk_base::CryptString & password,
+ bool allow_plain) : jid_(jid), password_(password),
+ allow_plain_(allow_plain) {}
+
+ virtual ~PlainSaslHandler() {}
+
+ // Should pick the best method according to this handler
+ // returns the empty string if none are suitable
+ virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+
+ if (!encrypted && !allow_plain_) {
+ return "";
+ }
+
+ std::vector<std::string>::const_iterator it = std::find(mechanisms.begin(), mechanisms.end(), "PLAIN");
+ if (it == mechanisms.end()) {
+ return "";
+ }
+ else {
+ return "PLAIN";
+ }
+ }
+
+ // Creates a SaslMechanism for the given mechanism name (you own it
+ // once you get it). If not handled, return NULL.
+ virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) {
+ if (mechanism == "PLAIN") {
+ return new SaslPlainMechanism(jid_, password_);
+ }
+ return NULL;
+ }
+
+private:
+ Jid jid_;
+ talk_base::CryptString password_;
+ bool allow_plain_;
+};
+
+
+}
+
+#endif
+
diff --git a/talk/xmpp/prexmppauth.h b/talk/xmpp/prexmppauth.h
new file mode 100644
index 0000000..dce5e0b
--- /dev/null
+++ b/talk/xmpp/prexmppauth.h
@@ -0,0 +1,86 @@
+/*
+ * 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 _PREXMPPAUTH_H_
+#define _PREXMPPAUTH_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/saslhandler.h"
+
+namespace talk_base {
+ class SocketAddress;
+}
+
+namespace buzz {
+
+class Jid;
+class SaslMechanism;
+
+class CaptchaChallenge {
+ public:
+ CaptchaChallenge() : captcha_needed_(false) {}
+ CaptchaChallenge(const std::string& token, const std::string& url)
+ : captcha_needed_(true), captcha_token_(token), captcha_image_url_(url) {
+ }
+
+ bool captcha_needed() const { return captcha_needed_; }
+ const std::string& captcha_token() const { return captcha_token_; }
+
+ // This url is relative to the gaia server. Once we have better tools
+ // for cracking URLs, we should probably make this a full URL
+ const std::string& captcha_image_url() const { return captcha_image_url_; }
+
+ private:
+ bool captcha_needed_;
+ std::string captcha_token_;
+ std::string captcha_image_url_;
+};
+
+class PreXmppAuth : public SaslHandler {
+public:
+ virtual ~PreXmppAuth() {}
+
+ virtual void StartPreXmppAuth(
+ const Jid & jid,
+ const talk_base::SocketAddress & server,
+ const talk_base::CryptString & pass,
+ const std::string & auth_cookie) = 0;
+
+ sigslot::signal0<> SignalAuthDone;
+
+ virtual bool IsAuthDone() const = 0;
+ virtual bool IsAuthorized() const = 0;
+ virtual bool HadError() const = 0;
+ virtual int GetError() const = 0;
+ virtual CaptchaChallenge GetCaptchaChallenge() const = 0;
+ virtual std::string GetAuthCookie() const = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/ratelimitmanager.cc b/talk/xmpp/ratelimitmanager.cc
new file mode 100644
index 0000000..14667a7
--- /dev/null
+++ b/talk/xmpp/ratelimitmanager.cc
@@ -0,0 +1,80 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 <list>
+#include <string>
+
+#include "talk/xmpp/ratelimitmanager.h"
+
+namespace buzz {
+
+RateLimitManager::RateLimit* RateLimitManager::GetRateLimit(
+ const std::string event_name) {
+ RateLimitMap::iterator it = rate_limits_.find(event_name);
+ if (it != rate_limits_.end()) {
+ return it->second;
+ }
+ return NULL;
+}
+
+bool RateLimitManager::IsWithinRateLimit(const std::string event_name) {
+ RateLimit* current_rate = GetRateLimit(event_name);
+ if (current_rate) {
+ return current_rate->IsWithinRateLimit();
+ }
+ return true; // If no rate limit is set, then you must be under the limit
+}
+
+void RateLimitManager::UpdateRateLimit(const std::string event_name,
+ int max_count,
+ int per_x_seconds) {
+ RateLimit* current_rate = GetRateLimit(event_name);
+ if (!current_rate) {
+ current_rate = new RateLimit(max_count, per_x_seconds);
+ rate_limits_[event_name] = current_rate;
+ }
+ current_rate->UpdateRateLimit();
+}
+
+bool RateLimitManager::VerifyRateLimit(const std::string event_name,
+ int max_count,
+ int per_x_seconds) {
+ return VerifyRateLimit(event_name, max_count, per_x_seconds, false);
+}
+
+bool RateLimitManager::VerifyRateLimit(const std::string event_name,
+ int max_count,
+ int per_x_seconds,
+ bool always_update) {
+ bool within_rate_limit = IsWithinRateLimit(event_name);
+ if (within_rate_limit || always_update) {
+ UpdateRateLimit(event_name, max_count, per_x_seconds);
+ }
+ return within_rate_limit;
+}
+
+}
diff --git a/talk/xmpp/ratelimitmanager.h b/talk/xmpp/ratelimitmanager.h
new file mode 100644
index 0000000..1a7fc82
--- /dev/null
+++ b/talk/xmpp/ratelimitmanager.h
@@ -0,0 +1,141 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 _RATELIMITMANAGER_H_
+#define _RATELIMITMANAGER_H_
+
+#include "talk/base/time.h"
+#include "talk/base/taskrunner.h"
+#include <map>
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// RATELIMITMANAGER
+//
+/////////////////////////////////////////////////////////////////////
+//
+// RateLimitManager imposes client-side rate limiting for xmpp tasks and
+// other events. It ensures that no more than i events with a given name
+// can occur within k seconds.
+//
+// A buffer tracks the previous max_count events. Before an event is allowed
+// to occur, it can check its rate limit with a call to VerifyRateLimit.
+// VerifyRateLimit will look up the i-th to last event and if more than
+// k seconds have passed since then, it will return true and update the
+// appropriate rate limits. Else, it will return false.
+//
+/////////////////////////////////////////////////////////////////////
+
+class RateLimitManager {
+ public:
+
+ RateLimitManager() { };
+ ~RateLimitManager() {
+ for (RateLimitMap::iterator it = rate_limits_.begin();
+ it != rate_limits_.end(); ++it) {
+ delete it->second;
+ }
+ };
+
+ // Checks if the event is under the defined rate limit and updates the
+ // rate limit if so. Returns true if it's under the rate limit.
+ bool VerifyRateLimit(const std::string event_name, int max_count,
+ int per_x_seconds);
+
+ // Checks if the event is under the defined rate limit and updates the
+ // rate limit if so *or* if always_update = true.
+ bool VerifyRateLimit(const std::string event_name, int max_count,
+ int per_x_seconds, bool always_update);
+
+ private:
+ class RateLimit {
+ public:
+ RateLimit(int max, int per_x_secs) : counter_(0), max_count_(max),
+ per_x_seconds_(per_x_secs) {
+ event_times_ = new uint32[max_count_];
+ for (int i = 0; i < max_count_; i++) {
+ event_times_[i] = 0;
+ }
+ }
+
+ ~RateLimit() {
+ if (event_times_) {
+ delete[] event_times_;
+ }
+ }
+
+ // True iff the current time >= to the next song allowed time
+ bool IsWithinRateLimit() {
+ return (talk_base::TimeSince(NextTimeAllowedForCounter()) >= 0);
+ }
+
+ // Updates time and counter for rate limit
+ void UpdateRateLimit() {
+ event_times_[counter_] = talk_base::Time();
+ counter_ = (counter_ + 1) % max_count_;
+ }
+
+ private:
+
+ // The time at which the i-th (where i = max_count) event occured
+ uint32 PreviousTimeAtCounter() {
+ return event_times_[counter_];
+ }
+
+ // The time that the next event is allowed to occur
+ uint32 NextTimeAllowedForCounter() {
+ return PreviousTimeAtCounter() + per_x_seconds_ * talk_base::kSecToMsec;
+ }
+
+ int counter_; // count modulo max_count of the current event
+ int max_count_; // max number of events that can occur within per_x_seconds
+ int per_x_seconds_; // interval size for rate limit
+ uint32* event_times_; // buffer of previous max_count event
+ };
+
+ typedef std::map<const std::string, RateLimit*> RateLimitMap;
+
+ // Maps from event name to its rate limit
+ RateLimitMap rate_limits_;
+
+ // Returns rate limit for event with specified name
+ RateLimit* GetRateLimit(const std::string event_name);
+
+ // True iff the current time >= to the next song allowed time
+ bool IsWithinRateLimit(const std::string event_name);
+
+ // Updates time and counter for rate limit
+ void UpdateRateLimit(const std::string event_name, int max_count,
+ int per_x_seconds);
+
+};
+
+}
+
+#endif //_RATELIMITMANAGER_H_
diff --git a/talk/xmpp/saslcookiemechanism.h b/talk/xmpp/saslcookiemechanism.h
new file mode 100644
index 0000000..92cff4d
--- /dev/null
+++ b/talk/xmpp/saslcookiemechanism.h
@@ -0,0 +1,88 @@
+/*
+ * 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 _SASLCOOKIEMECHANISM_H_
+#define _SASLCOOKIEMECHANISM_H_
+
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class SaslCookieMechanism : public SaslMechanism {
+
+public:
+ SaslCookieMechanism(const std::string & mechanism,
+ const std::string & username,
+ const std::string & cookie,
+ const std::string & token_service)
+ : mechanism_(mechanism),
+ username_(username),
+ cookie_(cookie),
+ token_service_(token_service) {}
+
+ SaslCookieMechanism(const std::string & mechanism,
+ const std::string & username,
+ const std::string & cookie)
+ : mechanism_(mechanism),
+ username_(username),
+ cookie_(cookie),
+ token_service_("") {}
+
+ virtual std::string GetMechanismName() { return mechanism_; }
+
+ virtual XmlElement * StartSaslAuth() {
+ // send initial request
+ XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+ el->AddAttr(QN_MECHANISM, mechanism_);
+ if (!token_service_.empty()) {
+ el->AddAttr(
+ QName(true, "http://www.google.com/talk/protocol/auth", "service"),
+ token_service_);
+ }
+
+ std::string credential;
+ credential.append("\0", 1);
+ credential.append(username_);
+ credential.append("\0", 1);
+ credential.append(cookie_);
+ el->AddText(Base64Encode(credential));
+ return el;
+ }
+
+private:
+ std::string mechanism_;
+ std::string username_;
+ std::string cookie_;
+ std::string token_service_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslhandler.h b/talk/xmpp/saslhandler.h
new file mode 100644
index 0000000..bead8aa
--- /dev/null
+++ b/talk/xmpp/saslhandler.h
@@ -0,0 +1,59 @@
+/*
+ * 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 _SASLHANDLER_H_
+#define _SASLHANDLER_H_
+
+#include <string>
+#include <vector>
+
+namespace buzz {
+
+class XmlElement;
+class SaslMechanism;
+
+// Creates mechanisms to deal with a given mechanism
+class SaslHandler {
+
+public:
+
+ // Intended to be subclassed
+ virtual ~SaslHandler() {}
+
+ // Should pick the best method according to this handler
+ // returns the empty string if none are suitable
+ virtual std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) = 0;
+
+ // Creates a SaslMechanism for the given mechanism name (you own it
+ // once you get it).
+ // If not handled, return NULL.
+ virtual SaslMechanism * CreateSaslMechanism(const std::string & mechanism) = 0;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslmechanism.cc b/talk/xmpp/saslmechanism.cc
new file mode 100644
index 0000000..2645ac0
--- /dev/null
+++ b/talk/xmpp/saslmechanism.cc
@@ -0,0 +1,72 @@
+/*
+ * 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 "talk/base/base64.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/saslmechanism.h"
+
+using talk_base::Base64;
+
+namespace buzz {
+
+XmlElement *
+SaslMechanism::StartSaslAuth() {
+ return new XmlElement(QN_SASL_AUTH, true);
+}
+
+XmlElement *
+SaslMechanism::HandleSaslChallenge(const XmlElement * challenge) {
+ return new XmlElement(QN_SASL_ABORT, true);
+}
+
+void
+SaslMechanism::HandleSaslSuccess(const XmlElement * success) {
+}
+
+void
+SaslMechanism::HandleSaslFailure(const XmlElement * failure) {
+}
+
+std::string
+SaslMechanism::Base64Encode(const std::string & plain) {
+ return Base64::Encode(plain);
+}
+
+std::string
+SaslMechanism::Base64Decode(const std::string & encoded) {
+ return Base64::Decode(encoded, Base64::DO_LAX);
+}
+
+std::string
+SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) {
+ std::string result;
+ Base64::EncodeFromArray(plain, length, &result);
+ return result;
+}
+
+}
diff --git a/talk/xmpp/saslmechanism.h b/talk/xmpp/saslmechanism.h
new file mode 100644
index 0000000..f2e5adc
--- /dev/null
+++ b/talk/xmpp/saslmechanism.h
@@ -0,0 +1,74 @@
+/*
+ * 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 _SASLMECHANISM_H_
+#define _SASLMECHANISM_H_
+
+#include <string>
+
+namespace buzz {
+
+class XmlElement;
+
+
+// Defines a mechnanism to do SASL authentication.
+// Subclass instances should have a self-contained way to present
+// credentials.
+class SaslMechanism {
+
+public:
+
+ // Intended to be subclassed
+ virtual ~SaslMechanism() {}
+
+ // Should return the name of the SASL mechanism, e.g., "PLAIN"
+ virtual std::string GetMechanismName() = 0;
+
+ // Should generate the initial "auth" request. Default is just <auth/>.
+ virtual XmlElement * StartSaslAuth();
+
+ // Should respond to a SASL "<challenge>" request. Default is
+ // to abort (for mechanisms that do not do challenge-response)
+ virtual XmlElement * HandleSaslChallenge(const XmlElement * challenge);
+
+ // Notification of a SASL "<success>". Sometimes information
+ // is passed on success.
+ virtual void HandleSaslSuccess(const XmlElement * success);
+
+ // Notification of a SASL "<failure>". Sometimes information
+ // for the user is passed on failure.
+ virtual void HandleSaslFailure(const XmlElement * failure);
+
+protected:
+ static std::string Base64Encode(const std::string & plain);
+ static std::string Base64Decode(const std::string & encoded);
+ static std::string Base64EncodeFromArray(const char * plain, size_t length);
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/saslplainmechanism.h b/talk/xmpp/saslplainmechanism.h
new file mode 100644
index 0000000..72532e6
--- /dev/null
+++ b/talk/xmpp/saslplainmechanism.h
@@ -0,0 +1,65 @@
+/*
+ * 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 _SASLPLAINMECHANISM_H_
+#define _SASLPLAINMECHANISM_H_
+
+#include "talk/base/cryptstring.h"
+#include "talk/xmpp/saslmechanism.h"
+
+namespace buzz {
+
+class SaslPlainMechanism : public SaslMechanism {
+
+public:
+ SaslPlainMechanism(const buzz::Jid user_jid, const talk_base::CryptString & password) :
+ user_jid_(user_jid), password_(password) {}
+
+ virtual std::string GetMechanismName() { return "PLAIN"; }
+
+ virtual XmlElement * StartSaslAuth() {
+ // send initial request
+ XmlElement * el = new XmlElement(QN_SASL_AUTH, true);
+ el->AddAttr(QN_MECHANISM, "PLAIN");
+
+ talk_base::FormatCryptString credential;
+ credential.Append("\0", 1);
+ credential.Append(user_jid_.node());
+ credential.Append("\0", 1);
+ credential.Append(&password_);
+ el->AddText(Base64EncodeFromArray(credential.GetData(), credential.GetLength()));
+ return el;
+ }
+
+private:
+ Jid user_jid_;
+ talk_base::CryptString password_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppclient.cc b/talk/xmpp/xmppclient.cc
new file mode 100644
index 0000000..d772998
--- /dev/null
+++ b/talk/xmpp/xmppclient.cc
@@ -0,0 +1,407 @@
+/*
+ * 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 "xmppclient.h"
+#include "xmpptask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/saslplainmechanism.h"
+#include "talk/xmpp/prexmppauth.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/plainsaslhandler.h"
+
+namespace buzz {
+
+talk_base::TaskParent* XmppClient::GetParent(int code) {
+ if (code == XMPP_CLIENT_TASK_CODE)
+ return this;
+ else
+ return talk_base::Task::GetParent(code);
+}
+
+class XmppClient::Private :
+ public sigslot::has_slots<>,
+ public XmppSessionHandler,
+ public XmppOutputHandler {
+public:
+
+ Private(XmppClient * client) :
+ client_(client),
+ socket_(NULL),
+ engine_(NULL),
+ proxy_port_(0),
+ pre_engine_error_(XmppEngine::ERROR_NONE),
+ pre_engine_subcode_(0),
+ signal_closed_(false),
+ allow_plain_(false) {}
+
+ // the owner
+ XmppClient * const client_;
+
+ // the two main objects
+ talk_base::scoped_ptr<AsyncSocket> socket_;
+ talk_base::scoped_ptr<XmppEngine> engine_;
+ talk_base::scoped_ptr<PreXmppAuth> pre_auth_;
+ talk_base::CryptString pass_;
+ std::string auth_cookie_;
+ talk_base::SocketAddress server_;
+ std::string proxy_host_;
+ int proxy_port_;
+ XmppEngine::Error pre_engine_error_;
+ int pre_engine_subcode_;
+ CaptchaChallenge captcha_challenge_;
+ bool signal_closed_;
+ bool allow_plain_;
+
+ // implementations of interfaces
+ void OnStateChange(int state);
+ void WriteOutput(const char * bytes, size_t len);
+ void StartTls(const std::string & domainname);
+ void CloseConnection();
+
+ // slots for socket signals
+ void OnSocketConnected();
+ void OnSocketRead();
+ void OnSocketClosed();
+};
+
+XmppReturnStatus
+XmppClient::Connect(const XmppClientSettings & settings, const std::string & lang, AsyncSocket * socket, PreXmppAuth * pre_auth) {
+ if (socket == NULL)
+ return XMPP_RETURN_BADARGUMENT;
+ if (d_->socket_.get() != NULL)
+ return XMPP_RETURN_BADSTATE;
+
+ d_->socket_.reset(socket);
+
+ d_->socket_->SignalConnected.connect(d_.get(), &Private::OnSocketConnected);
+ d_->socket_->SignalRead.connect(d_.get(), &Private::OnSocketRead);
+ d_->socket_->SignalClosed.connect(d_.get(), &Private::OnSocketClosed);
+
+ d_->engine_.reset(XmppEngine::Create());
+ d_->engine_->SetSessionHandler(d_.get());
+ d_->engine_->SetOutputHandler(d_.get());
+ if (!settings.resource().empty()) {
+ d_->engine_->SetRequestedResource(settings.resource());
+ }
+ d_->engine_->SetUseTls(settings.use_tls());
+
+ //
+ // The talk.google.com server expects you to use "gmail.com" in the
+ // stream, and expects the domain certificate to be "gmail.com" as well.
+ // For all other servers, we leave the strings empty, which causes
+ // the jid's domain to be used. "foo@example.com" -> stream to="example.com"
+ // tls certificate for "example.com"
+ //
+ // This is only true when using Gaia auth, so let's say if there's no preauth,
+ // we should use the actual server name
+ std::string server_name = settings.server().IPAsString();
+ if ((server_name == buzz::STR_TALK_GOOGLE_COM ||
+ server_name == buzz::STR_TALKX_L_GOOGLE_COM) &&
+ pre_auth != NULL) {
+ d_->engine_->SetTlsServer(buzz::STR_GMAIL_COM, buzz::STR_GMAIL_COM);
+ }
+
+ // Set language
+ d_->engine_->SetLanguage(lang);
+
+ d_->engine_->SetUser(buzz::Jid(settings.user(), settings.host(), STR_EMPTY));
+
+ d_->pass_ = settings.pass();
+ d_->auth_cookie_ = settings.auth_cookie();
+ d_->server_ = settings.server();
+ d_->proxy_host_ = settings.proxy_host();
+ d_->proxy_port_ = settings.proxy_port();
+ d_->allow_plain_ = settings.allow_plain();
+ d_->pre_auth_.reset(pre_auth);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppEngine::State
+XmppClient::GetState() {
+ if (d_->engine_.get() == NULL)
+ return XmppEngine::STATE_NONE;
+ return d_->engine_->GetState();
+}
+
+XmppEngine::Error
+XmppClient::GetError(int *subcode) {
+ if (subcode) {
+ *subcode = 0;
+ }
+ if (d_->engine_.get() == NULL)
+ return XmppEngine::ERROR_NONE;
+ if (d_->pre_engine_error_ != XmppEngine::ERROR_NONE) {
+ if (subcode) {
+ *subcode = d_->pre_engine_subcode_;
+ }
+ return d_->pre_engine_error_;
+ }
+ return d_->engine_->GetError(subcode);
+}
+
+const XmlElement *
+XmppClient::GetStreamError() {
+ if (d_->engine_.get() == NULL) {
+ return NULL;
+ }
+ return d_->engine_->GetStreamError();
+}
+
+CaptchaChallenge XmppClient::GetCaptchaChallenge() {
+ if (d_->engine_.get() == NULL)
+ return CaptchaChallenge();
+ return d_->captcha_challenge_;
+}
+
+std::string
+XmppClient::GetAuthCookie() {
+ if (d_->engine_.get() == NULL)
+ return "";
+ return d_->auth_cookie_;
+}
+
+int
+XmppClient::ProcessStart() {
+ if (d_->pre_auth_.get()) {
+ d_->pre_auth_->SignalAuthDone.connect(this, &XmppClient::OnAuthDone);
+ d_->pre_auth_->StartPreXmppAuth(
+ d_->engine_->GetUser(), d_->server_, d_->pass_, d_->auth_cookie_);
+ d_->pass_.Clear(); // done with this;
+ return STATE_PRE_XMPP_LOGIN;
+ }
+ else {
+ d_->engine_->SetSaslHandler(new PlainSaslHandler(
+ d_->engine_->GetUser(), d_->pass_, d_->allow_plain_));
+ d_->pass_.Clear(); // done with this;
+ return STATE_START_XMPP_LOGIN;
+ }
+}
+
+void
+XmppClient::OnAuthDone() {
+ Wake();
+}
+
+int
+XmppClient::ProcessCookieLogin() {
+ // Don't know how this could happen, but crash reports show it as NULL
+ if (!d_->pre_auth_.get()) {
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ // Wait until pre authentication is done is done
+ if (!d_->pre_auth_->IsAuthDone())
+ return STATE_BLOCKED;
+
+ if (!d_->pre_auth_->IsAuthorized()) {
+ // maybe split out a case when gaia is down?
+ if (d_->pre_auth_->HadError()) {
+ d_->pre_engine_error_ = XmppEngine::ERROR_AUTH;
+ d_->pre_engine_subcode_ = d_->pre_auth_->GetError();
+ }
+ else {
+ d_->pre_engine_error_ = XmppEngine::ERROR_UNAUTHORIZED;
+ d_->pre_engine_subcode_ = 0;
+ d_->captcha_challenge_ = d_->pre_auth_->GetCaptchaChallenge();
+ }
+ d_->pre_auth_.reset(NULL); // done with this
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ // Save auth cookie as a result
+ d_->auth_cookie_ = d_->pre_auth_->GetAuthCookie();
+
+ // transfer ownership of pre_auth_ to engine
+ d_->engine_->SetSaslHandler(d_->pre_auth_.release());
+ return STATE_START_XMPP_LOGIN;
+}
+
+int
+XmppClient::ProcessStartXmppLogin() {
+ // Done with pre-connect tasks - connect!
+ if (!d_->socket_->Connect(d_->server_)) {
+ EnsureClosed();
+ return STATE_ERROR;
+ }
+
+ return STATE_RESPONSE;
+}
+
+int
+XmppClient::ProcessResponse() {
+ // Hang around while we are connected.
+ if (!delivering_signal_ && (d_->engine_.get() == NULL ||
+ d_->engine_->GetState() == XmppEngine::STATE_CLOSED))
+ return STATE_DONE;
+ return STATE_BLOCKED;
+}
+
+XmppReturnStatus
+XmppClient::Disconnect() {
+ if (d_->socket_.get() == NULL)
+ return XMPP_RETURN_BADSTATE;
+ d_->engine_->Disconnect();
+ d_->socket_.reset(NULL);
+ return XMPP_RETURN_OK;
+}
+
+XmppClient::XmppClient(TaskParent * parent)
+ : Task(parent),
+ delivering_signal_(false),
+ valid_(false) {
+ d_.reset(new Private(this));
+ valid_ = true;
+}
+
+XmppClient::~XmppClient() {
+ valid_ = false;
+}
+
+const Jid &
+XmppClient::jid() {
+ return d_->engine_->FullJid();
+}
+
+
+std::string
+XmppClient::NextId() {
+ return d_->engine_->NextId();
+}
+
+XmppReturnStatus
+XmppClient::SendStanza(const XmlElement * stanza) {
+ return d_->engine_->SendStanza(stanza);
+}
+
+XmppReturnStatus
+XmppClient::SendStanzaError(const XmlElement * old_stanza, XmppStanzaError xse, const std::string & message) {
+ return d_->engine_->SendStanzaError(old_stanza, xse, message);
+}
+
+XmppReturnStatus
+XmppClient::SendRaw(const std::string & text) {
+ return d_->engine_->SendRaw(text);
+}
+
+XmppEngine*
+XmppClient::engine() {
+ return d_->engine_.get();
+}
+
+void
+XmppClient::Private::OnSocketConnected() {
+ engine_->Connect();
+}
+
+void
+XmppClient::Private::OnSocketRead() {
+ char bytes[4096];
+ size_t bytes_read;
+ for (;;) {
+ if (!socket_->Read(bytes, sizeof(bytes), &bytes_read)) {
+ // TODO: deal with error information
+ return;
+ }
+
+ if (bytes_read == 0)
+ return;
+
+//#ifdef _DEBUG
+ client_->SignalLogInput(bytes, bytes_read);
+//#endif
+
+ engine_->HandleInput(bytes, bytes_read);
+ }
+}
+
+void
+XmppClient::Private::OnSocketClosed() {
+ int code = socket_->GetError();
+ engine_->ConnectionClosed(code);
+}
+
+void
+XmppClient::Private::OnStateChange(int state) {
+ if (state == XmppEngine::STATE_CLOSED) {
+ client_->EnsureClosed();
+ }
+ else {
+ client_->SignalStateChange((XmppEngine::State)state);
+ }
+ client_->Wake();
+}
+
+void
+XmppClient::Private::WriteOutput(const char * bytes, size_t len) {
+
+//#ifdef _DEBUG
+ client_->SignalLogOutput(bytes, len);
+//#endif
+
+ socket_->Write(bytes, len);
+ // TODO: deal with error information
+}
+
+void
+XmppClient::Private::StartTls(const std::string & domain) {
+#if defined(FEATURE_ENABLE_SSL)
+ socket_->StartTls(domain);
+#endif
+}
+
+void
+XmppClient::Private::CloseConnection() {
+ socket_->Close();
+}
+
+void
+XmppClient::AddXmppTask(XmppTask * task, XmppEngine::HandlerLevel level) {
+ d_->engine_->AddStanzaHandler(task, level);
+}
+
+void
+XmppClient::RemoveXmppTask(XmppTask * task) {
+ d_->engine_->RemoveStanzaHandler(task);
+}
+
+void
+XmppClient::EnsureClosed() {
+ if (!d_->signal_closed_) {
+ d_->signal_closed_ = true;
+ delivering_signal_ = true;
+ SignalStateChange(XmppEngine::STATE_CLOSED);
+ delivering_signal_ = false;
+ }
+}
+
+
+}
diff --git a/talk/xmpp/xmppclient.h b/talk/xmpp/xmppclient.h
new file mode 100644
index 0000000..9983794
--- /dev/null
+++ b/talk/xmpp/xmppclient.h
@@ -0,0 +1,163 @@
+/*
+ * 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 _XMPPCLIENT_H_
+#define _XMPPCLIENT_H_
+
+#include <string>
+#include "talk/base/basicdefs.h"
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/asyncsocket.h"
+#include "talk/xmpp/xmppclientsettings.h"
+#include "talk/base/task.h"
+
+namespace buzz {
+
+class XmppTask;
+class PreXmppAuth;
+class CaptchaChallenge;
+
+// Just some non-colliding number. Could have picked "1".
+#define XMPP_CLIENT_TASK_CODE 0x366c1e47
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPCLIENT
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task first. XmppClient is a parent task for XmppTasks.
+//
+// XmppClient is a task which is designed to be the parent task for
+// all tasks that depend on a single Xmpp connection. If you want to,
+// for example, listen for subscription requests forever, then your
+// listener should be a task that is a child of the XmppClient that owns
+// the connection you are using. XmppClient has all the utility methods
+// that basically drill through to XmppEngine.
+//
+// XmppClient is just a wrapper for XmppEngine, and if I were writing it
+// all over again, I would make XmppClient == XmppEngine. Why?
+// XmppEngine needs tasks too, for example it has an XmppLoginTask which
+// should just be the same kind of Task instead of an XmppEngine specific
+// thing. It would help do certain things like GAIA auth cleaner.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient : public talk_base::Task, public sigslot::has_slots<>
+{
+public:
+ explicit XmppClient(talk_base::TaskParent * parent);
+ ~XmppClient();
+
+ XmppReturnStatus Connect(const XmppClientSettings & settings,
+ const std::string & lang,
+ AsyncSocket * socket,
+ PreXmppAuth * preauth);
+
+ virtual talk_base::TaskParent* GetParent(int code);
+ virtual int ProcessStart();
+ virtual int ProcessResponse();
+ XmppReturnStatus Disconnect();
+ const Jid & jid();
+
+ sigslot::signal1<XmppEngine::State> SignalStateChange;
+ XmppEngine::State GetState();
+ XmppEngine::Error GetError(int *subcode);
+
+ // When there is a <stream:error> stanza, return the stanza
+ // so that they can be handled.
+ const XmlElement *GetStreamError();
+
+ // When there is an authentication error, we may have captcha info
+ // that the user can use to unlock their account
+ CaptchaChallenge GetCaptchaChallenge();
+
+ // When authentication is successful, this returns the service cookie
+ // (if we used GAIA authentication)
+ std::string GetAuthCookie();
+
+ std::string NextId();
+ XmppReturnStatus SendStanza(const XmlElement *stanza);
+ XmppReturnStatus SendRaw(const std::string & text);
+ XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text);
+
+ XmppEngine* engine();
+
+ sigslot::signal2<const char *, int> SignalLogInput;
+ sigslot::signal2<const char *, int> SignalLogOutput;
+
+private:
+ friend class XmppTask;
+
+ void OnAuthDone();
+
+ // managed tasks and dispatching
+ void AddXmppTask(XmppTask *, XmppEngine::HandlerLevel);
+ void RemoveXmppTask(XmppTask *);
+
+ sigslot::signal0<> SignalDisconnected;
+
+private:
+ // Internal state management
+ enum {
+ STATE_PRE_XMPP_LOGIN = STATE_NEXT,
+ STATE_START_XMPP_LOGIN = STATE_NEXT + 1,
+ };
+ int Process(int state) {
+ switch (state) {
+ case STATE_PRE_XMPP_LOGIN: return ProcessCookieLogin();
+ case STATE_START_XMPP_LOGIN: return ProcessStartXmppLogin();
+ default: return Task::Process(state);
+ }
+ }
+
+ std::string GetStateName(int state) const {
+ switch (state) {
+ case STATE_PRE_XMPP_LOGIN: return "PRE_XMPP_LOGIN";
+ case STATE_START_XMPP_LOGIN: return "START_XMPP_LOGIN";
+ default: return Task::GetStateName(state);
+ }
+ }
+
+ int ProcessCookieLogin();
+ int ProcessStartXmppLogin();
+ void EnsureClosed();
+
+ class Private;
+ friend class Private;
+ talk_base::scoped_ptr<Private> d_;
+
+ bool delivering_signal_;
+ bool valid_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppclientsettings.h b/talk/xmpp/xmppclientsettings.h
new file mode 100644
index 0000000..4f821ab
--- /dev/null
+++ b/talk/xmpp/xmppclientsettings.h
@@ -0,0 +1,116 @@
+/*
+ * 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 _XMPPCLIENTSETTINGS_H_
+#define _XMPPCLIENTSETTINGS_H_
+
+#include "talk/p2p/base/port.h"
+#include "talk/base/cryptstring.h"
+
+namespace buzz {
+
+class XmppUserSettings {
+ public:
+ XmppUserSettings()
+ : use_tls_(false),
+ allow_plain_(false) {
+ }
+
+ void set_user(const std::string & user) { user_ = user; }
+ void set_host(const std::string & host) { host_ = host; }
+ void set_pass(const talk_base::CryptString & pass) { pass_ = pass; }
+ void set_auth_cookie(const std::string & cookie) { auth_cookie_ = cookie; }
+ void set_resource(const std::string & resource) { resource_ = resource; }
+ void set_use_tls(bool use_tls) { use_tls_ = use_tls; }
+ void set_allow_plain(bool f) { allow_plain_ = f; }
+ void set_token_service(const std::string & token_service) {
+ token_service_ = token_service;
+ }
+
+ const std::string & user() const { return user_; }
+ const std::string & host() const { return host_; }
+ const talk_base::CryptString & pass() const { return pass_; }
+ const std::string & auth_cookie() const { return auth_cookie_; }
+ const std::string & resource() const { return resource_; }
+ bool use_tls() const { return use_tls_; }
+ bool allow_plain() const { return allow_plain_; }
+ const std::string & token_service() const { return token_service_; }
+
+ private:
+ std::string user_;
+ std::string host_;
+ talk_base::CryptString pass_;
+ std::string auth_cookie_;
+ std::string resource_;
+ bool use_tls_;
+ bool allow_plain_;
+ std::string token_service_;
+};
+
+class XmppClientSettings : public XmppUserSettings {
+ public:
+ XmppClientSettings()
+ : protocol_(cricket::PROTO_TCP),
+ proxy_(talk_base::PROXY_NONE),
+ proxy_port_(80),
+ use_proxy_auth_(false) {
+ }
+
+ void set_server(const talk_base::SocketAddress & server) {
+ server_ = server;
+ }
+ void set_protocol(cricket::ProtocolType protocol) { protocol_ = protocol; }
+ void set_proxy(talk_base::ProxyType f) { proxy_ = f; }
+ void set_proxy_host(const std::string & host) { proxy_host_ = host; }
+ void set_proxy_port(int port) { proxy_port_ = port; };
+ void set_use_proxy_auth(bool f) { use_proxy_auth_ = f; }
+ void set_proxy_user(const std::string & user) { proxy_user_ = user; }
+ void set_proxy_pass(const talk_base::CryptString & pass) { proxy_pass_ = pass; }
+
+ const talk_base::SocketAddress & server() const { return server_; }
+ cricket::ProtocolType protocol() const { return protocol_; }
+ talk_base::ProxyType proxy() const { return proxy_; }
+ const std::string & proxy_host() const { return proxy_host_; }
+ int proxy_port() const { return proxy_port_; }
+ bool use_proxy_auth() const { return use_proxy_auth_; }
+ const std::string & proxy_user() const { return proxy_user_; }
+ const talk_base::CryptString & proxy_pass() const { return proxy_pass_; }
+
+ private:
+ talk_base::SocketAddress server_;
+ cricket::ProtocolType protocol_;
+ talk_base::ProxyType proxy_;
+ std::string proxy_host_;
+ int proxy_port_;
+ bool use_proxy_auth_;
+ std::string proxy_user_;
+ talk_base::CryptString proxy_pass_;
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppengine.h b/talk/xmpp/xmppengine.h
new file mode 100644
index 0000000..44ed83d
--- /dev/null
+++ b/talk/xmpp/xmppengine.h
@@ -0,0 +1,341 @@
+/*
+ * 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 _xmppengine_h_
+#define _xmppengine_h_
+
+// also part of the API
+#include "talk/xmpp/jid.h"
+#include "talk/xmllite/qname.h"
+#include "talk/xmllite/xmlelement.h"
+
+
+namespace buzz {
+
+class XmppEngine;
+class SaslHandler;
+typedef void * XmppIqCookie;
+
+//! XMPP stanza error codes.
+//! Used in XmppEngine.SendStanzaError().
+enum XmppStanzaError {
+ XSE_BAD_REQUEST,
+ XSE_CONFLICT,
+ XSE_FEATURE_NOT_IMPLEMENTED,
+ XSE_FORBIDDEN,
+ XSE_GONE,
+ XSE_INTERNAL_SERVER_ERROR,
+ XSE_ITEM_NOT_FOUND,
+ XSE_JID_MALFORMED,
+ XSE_NOT_ACCEPTABLE,
+ XSE_NOT_ALLOWED,
+ XSE_PAYMENT_REQUIRED,
+ XSE_RECIPIENT_UNAVAILABLE,
+ XSE_REDIRECT,
+ XSE_REGISTRATION_REQUIRED,
+ XSE_SERVER_NOT_FOUND,
+ XSE_SERVER_TIMEOUT,
+ XSE_RESOURCE_CONSTRAINT,
+ XSE_SERVICE_UNAVAILABLE,
+ XSE_SUBSCRIPTION_REQUIRED,
+ XSE_UNDEFINED_CONDITION,
+ XSE_UNEXPECTED_REQUEST,
+};
+
+// XmppReturnStatus
+// This is used by API functions to synchronously return status.
+enum XmppReturnStatus {
+ XMPP_RETURN_OK,
+ XMPP_RETURN_BADARGUMENT,
+ XMPP_RETURN_BADSTATE,
+ XMPP_RETURN_PENDING,
+ XMPP_RETURN_UNEXPECTED,
+ XMPP_RETURN_NOTYETIMPLEMENTED,
+};
+
+//! Callback for socket output for an XmppEngine connection.
+//! Register via XmppEngine.SetOutputHandler. An XmppEngine
+//! can call back to this handler while it is processing
+//! Connect, SendStanza, SendIq, Disconnect, or HandleInput.
+class XmppOutputHandler {
+public:
+ virtual ~XmppOutputHandler() {}
+
+ //! Deliver the specified bytes to the XMPP socket.
+ virtual void WriteOutput(const char * bytes, size_t len) = 0;
+
+ //! Initiate TLS encryption on the socket.
+ //! The implementation must verify that the SSL
+ //! certificate matches the given domainname.
+ virtual void StartTls(const std::string & domainname) = 0;
+
+ //! Called when engine wants the connecton closed.
+ virtual void CloseConnection() = 0;
+};
+
+//! Callback to deliver engine state change notifications
+//! to the object managing the engine.
+class XmppSessionHandler {
+public:
+ virtual ~XmppSessionHandler() {}
+ //! Called when engine changes state. Argument is new state.
+ virtual void OnStateChange(int state) = 0;
+};
+
+//! Callback to deliver stanzas to an Xmpp application module.
+//! Register via XmppEngine.SetDefaultSessionHandler or via
+//! XmppEngine.AddSessionHAndler.
+class XmppStanzaHandler {
+public:
+ virtual ~XmppStanzaHandler() {}
+ //! Process the given stanza.
+ //! The handler must return true if it has handled the stanza.
+ //! A false return value causes the stanza to be passed on to
+ //! the next registered handler.
+ virtual bool HandleStanza(const XmlElement * stanza) = 0;
+};
+
+//! Callback to deliver iq responses (results and errors).
+//! Register while sending an iq via XmppEngine.SendIq.
+//! Iq responses are routed to matching XmppIqHandlers in preference
+//! to sending to any registered SessionHandlers.
+class XmppIqHandler {
+public:
+ virtual ~XmppIqHandler() {}
+ //! Called to handle the iq response.
+ //! The response may be either a result or an error, and will have
+ //! an 'id' that matches the request and a 'from' that matches the
+ //! 'to' of the request. Called no more than once; once this is
+ //! called, the handler is automatically unregistered.
+ virtual void IqResponse(XmppIqCookie cookie, const XmlElement * pelStanza) = 0;
+};
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput. Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngine {
+public:
+ static XmppEngine * Create();
+ virtual ~XmppEngine() {}
+
+ //! Error codes. See GetError().
+ enum Error {
+ ERROR_NONE = 0, //!< No error
+ ERROR_XML, //!< Malformed XML or encoding error
+ ERROR_STREAM, //!< XMPP stream error - see GetStreamError()
+ ERROR_VERSION, //!< XMPP version error
+ ERROR_UNAUTHORIZED, //!< User is not authorized (rejected credentials)
+ ERROR_TLS, //!< TLS could not be negotiated
+ ERROR_AUTH, //!< Authentication could not be negotiated
+ ERROR_BIND, //!< Resource or session binding could not be negotiated
+ ERROR_CONNECTION_CLOSED,//!< Connection closed by output handler.
+ ERROR_DOCUMENT_CLOSED, //!< Closed by </stream:stream>
+ ERROR_SOCKET, //!< Socket error
+ ERROR_NETWORK_TIMEOUT, //!< Some sort of timeout (eg., we never got the roster)
+ ERROR_MISSING_USERNAME //!< User has a Google Account but no nickname
+ };
+
+ //! States. See GetState().
+ enum State {
+ STATE_NONE = 0, //!< Nonexistent state
+ STATE_START, //!< Initial state.
+ STATE_OPENING, //!< Exchanging stream headers, authenticating and so on.
+ STATE_OPEN, //!< Authenticated and bound.
+ STATE_CLOSED, //!< Session closed, possibly due to error.
+ };
+
+ // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+ //! Registers the handler for socket output
+ virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh) = 0;
+
+ //! Provides socket input to the engine
+ virtual XmppReturnStatus HandleInput(const char * bytes, size_t len) = 0;
+
+ //! Advises the engine that the socket has closed
+ virtual XmppReturnStatus ConnectionClosed(int subcode) = 0;
+
+ // SESSION SETUP ---------------------------------------------------------
+
+ //! Indicates the (bare) JID for the user to use.
+ virtual XmppReturnStatus SetUser(const Jid & jid)= 0;
+
+ //! Get the login (bare) JID.
+ virtual const Jid & GetUser() = 0;
+
+ //! Provides different methods for credentials for login.
+ //! Takes ownership of this object; deletes when login is done
+ virtual XmppReturnStatus SetSaslHandler(SaslHandler * h) = 0;
+
+ //! Sets whether TLS will be used within the connection (default true).
+ virtual XmppReturnStatus SetUseTls(bool useTls) = 0;
+
+ //! Sets an alternate domain from which we allows TLS certificates.
+ //! This is for use in the case where a we want to allow a proxy to
+ //! serve up its own certificate rather than one owned by the underlying
+ //! domain.
+ virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname,
+ const std::string & proxy_domain) = 0;
+
+ //! Gets whether TLS will be used within the connection.
+ virtual bool GetUseTls() = 0;
+
+ //! Sets the request resource name, if any (optional).
+ //! Note that the resource name may be overridden by the server; after
+ //! binding, the actual resource name is available as part of FullJid().
+ virtual XmppReturnStatus SetRequestedResource(const std::string& resource) = 0;
+
+ //! Gets the request resource name.
+ virtual const std::string & GetRequestedResource() = 0;
+
+ //! Sets language
+ virtual void SetLanguage(const std::string & lang) = 0;
+
+ // SESSION MANAGEMENT ---------------------------------------------------
+
+ //! Set callback for state changes.
+ virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler) = 0;
+
+ //! Initiates the XMPP connection.
+ //! After supplying connection settings, call this once to initiate,
+ //! (optionally) encrypt, authenticate, and bind the connection.
+ virtual XmppReturnStatus Connect() = 0;
+
+ //! The current engine state.
+ virtual State GetState() = 0;
+
+ //! Returns true if the connection is encrypted (under TLS)
+ virtual bool IsEncrypted() = 0;
+
+ //! The error code.
+ //! Consult this after XmppOutputHandler.OnClose().
+ virtual Error GetError(int *subcode) = 0;
+
+ //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+ //! Notice the stanza returned is owned by the XmppEngine and
+ //! is deleted when the engine is destroyed.
+ virtual const XmlElement * GetStreamError() = 0;
+
+ //! Closes down the connection.
+ //! Sends CloseConnection to output, and disconnects and registered
+ //! session handlers. After Disconnect completes, it is guaranteed
+ //! that no further callbacks will be made.
+ virtual XmppReturnStatus Disconnect() = 0;
+
+ // APPLICATION USE -------------------------------------------------------
+
+ enum HandlerLevel {
+ HL_NONE = 0,
+ HL_PEEK, //!< Sees messages before all other processing; cannot abort
+ HL_SINGLE, //!< Watches for a single message, e.g., by id and sender
+ HL_SENDER, //!< Watches for a type of message from a specific sender
+ HL_TYPE, //!< Watches a type of message, e.g., all groupchat msgs
+ HL_ALL, //!< Watches all messages - gets last shot
+ HL_COUNT, //!< Count of handler levels
+ };
+
+ //! Adds a listener for session events.
+ //! Stanza delivery is chained to session handlers; the first to
+ //! return 'true' is the last to get each stanza.
+ virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler, HandlerLevel level = HL_PEEK) = 0;
+
+ //! Removes a listener for session events.
+ virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler) = 0;
+
+ //! Sends a stanza to the server.
+ virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza) = 0;
+
+ //! Sends raw text to the server
+ virtual XmppReturnStatus SendRaw(const std::string & text) = 0;
+
+ //! Sends an iq to the server, and registers a callback for the result.
+ //! Returns the cookie passed to the result handler.
+ virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+ XmppIqHandler* iq_handler,
+ XmppIqCookie* cookie) = 0;
+
+ //! Unregisters an iq callback handler given its cookie.
+ //! No callback will come to this handler after it's unregistered.
+ virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler** iq_handler) = 0;
+
+
+ //! Forms and sends an error in response to the given stanza.
+ //! Swaps to and from, sets type to "error", and adds error information
+ //! based on the passed code. Text is optional and may be STR_EMPTY.
+ virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text) = 0;
+
+ //! The fullly bound JID.
+ //! This JID is only valid after binding has succeeded. If the value
+ //! is JID_NULL, the binding has not succeeded.
+ virtual const Jid & FullJid() = 0;
+
+ //! The next unused iq id for this connection.
+ //! Call this when building iq stanzas, to ensure that each iq
+ //! gets its own unique id.
+ virtual std::string NextId() = 0;
+
+};
+
+}
+
+
+// Move these to a better location
+
+#define XMPP_FAILED(x) \
+ ( (x) == buzz::XMPP_RETURN_OK ? false : true) \
+
+
+#define XMPP_SUCCEEDED(x) \
+ ( (x) == buzz::XMPP_RETURN_OK ? true : false) \
+
+#define IFR(x) \
+ do { \
+ xmpp_status = (x); \
+ if (XMPP_FAILED(xmpp_status)) { \
+ return xmpp_status; \
+ } \
+ } while (false) \
+
+
+#define IFC(x) \
+ do { \
+ xmpp_status = (x); \
+ if (XMPP_FAILED(xmpp_status)) { \
+ goto Cleanup; \
+ } \
+ } while (false) \
+
+
+#endif
diff --git a/talk/xmpp/xmppengineimpl.cc b/talk/xmpp/xmppengineimpl.cc
new file mode 100644
index 0000000..262c531
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.cc
@@ -0,0 +1,498 @@
+/*
+ * 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.
+ */
+
+#define TRACK_ARRAY_ALLOC_PROBLEM
+
+#include <vector>
+#include <sstream>
+#include <algorithm>
+#include "talk/xmllite/xmlelement.h"
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/xmpplogintask.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmllite/xmlprinter.h"
+#include "talk/xmpp/saslhandler.h"
+
+namespace buzz {
+
+static const std::string XMPP_CLIENT_NAMESPACES[] = {
+ "stream", "http://etherx.jabber.org/streams",
+ "", "jabber:client",
+};
+
+static const size_t XMPP_CLIENT_NAMESPACES_LEN = 4;
+
+XmppEngine * XmppEngine::Create() {
+ return new XmppEngineImpl();
+}
+
+
+XmppEngineImpl::XmppEngineImpl() :
+ stanzaParseHandler_(this),
+ stanzaParser_(&stanzaParseHandler_),
+ engine_entered_(0),
+ user_jid_(JID_EMPTY),
+ password_(),
+ requested_resource_(STR_EMPTY),
+ tls_needed_(true),
+ login_task_(new XmppLoginTask(this)),
+ next_id_(0),
+ bound_jid_(JID_EMPTY),
+ state_(STATE_START),
+ encrypted_(false),
+ error_code_(ERROR_NONE),
+ subcode_(0),
+ stream_error_(NULL),
+ raised_reset_(false),
+ output_handler_(NULL),
+ session_handler_(NULL),
+ iq_entries_(new IqEntryVector()),
+ sasl_handler_(NULL),
+ output_(new std::stringstream()) {
+ for (int i = 0; i < HL_COUNT; i+= 1) {
+ stanza_handlers_[i].reset(new StanzaHandlerVector());
+ }
+}
+
+XmppEngineImpl::~XmppEngineImpl() {
+ DeleteIqCookies();
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetOutputHandler(XmppOutputHandler* output_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ output_handler_ = output_handler;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetSessionHandler(XmppSessionHandler* session_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ session_handler_ = session_handler;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::HandleInput(const char * bytes, size_t len) {
+ if (state_ < STATE_OPENING || state_ > STATE_OPEN)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ // TODO: The return value of the xml parser is not checked.
+ stanzaParser_.Parse(bytes, len, false);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::ConnectionClosed(int subcode) {
+ if (state_ != STATE_CLOSED) {
+ EnterExit ee(this);
+ // If told that connection closed and not already closed,
+ // then connection was unpexectedly dropped.
+ if (subcode) {
+ SignalError(ERROR_SOCKET, subcode);
+ } else {
+ SignalError(ERROR_CONNECTION_CLOSED, 0); // no subcode
+ }
+ }
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetUseTls(bool useTls) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ tls_needed_ = useTls;
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetTlsServer(const std::string & tls_server_hostname,
+ const std::string & tls_server_domain) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ tls_server_hostname_ = tls_server_hostname;
+ tls_server_domain_= tls_server_domain;
+
+ return XMPP_RETURN_OK;
+}
+
+bool
+XmppEngineImpl::GetUseTls() {
+ return tls_needed_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetUser(const Jid & jid) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ user_jid_ = jid;
+
+ return XMPP_RETURN_OK;
+}
+
+const Jid &
+XmppEngineImpl::GetUser() {
+ return user_jid_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetSaslHandler(SaslHandler * sasl_handler) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ sasl_handler_.reset(sasl_handler);
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SetRequestedResource(const std::string & resource) {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ requested_resource_ = resource;
+
+ return XMPP_RETURN_OK;
+}
+
+const std::string &
+XmppEngineImpl::GetRequestedResource() {
+ return requested_resource_;
+}
+
+XmppReturnStatus
+XmppEngineImpl::AddStanzaHandler(XmppStanzaHandler * stanza_handler,
+ XmppEngine::HandlerLevel level) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ stanza_handlers_[level]->push_back(stanza_handler);
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::RemoveStanzaHandler(XmppStanzaHandler * stanza_handler) {
+
+ bool found = false;
+
+ for (int level = 0; level < HL_COUNT; level += 1) {
+ StanzaHandlerVector::iterator new_end =
+ std::remove(stanza_handlers_[level]->begin(),
+ stanza_handlers_[level]->end(),
+ stanza_handler);
+
+ if (new_end != stanza_handlers_[level]->end()) {
+ stanza_handlers_[level]->erase(new_end, stanza_handlers_[level]->end());
+ found = true;
+ }
+ }
+
+ if (!found) {
+ return XMPP_RETURN_BADARGUMENT;
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::Connect() {
+ if (state_ != STATE_START)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ // get the login task started
+ state_ = STATE_OPENING;
+ if (login_task_.get()) {
+ login_task_->IncomingStanza(NULL, false);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SendStanza(const XmlElement * element) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ if (login_task_.get()) {
+ // still handshaking - then outbound stanzas are queued
+ login_task_->OutgoingStanza(element);
+ } else {
+ // handshake done - send straight through
+ InternalSendStanza(element);
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+XmppReturnStatus
+XmppEngineImpl::SendRaw(const std::string & text) {
+ if (state_ == STATE_CLOSED || login_task_.get())
+ return XMPP_RETURN_BADSTATE;
+
+ EnterExit ee(this);
+
+ (*output_) << text;
+
+ return XMPP_RETURN_OK;
+}
+
+std::string
+XmppEngineImpl::NextId() {
+ std::stringstream ss;
+ ss << next_id_++;
+ return ss.str();
+}
+
+XmppReturnStatus
+XmppEngineImpl::Disconnect() {
+
+ if (state_ != STATE_CLOSED) {
+ EnterExit ee(this);
+ if (state_ == STATE_OPEN)
+ *output_ << "</stream:stream>";
+ state_ = STATE_CLOSED;
+ }
+
+ return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::IncomingStart(const XmlElement * pelStart) {
+ if (HasError() || raised_reset_)
+ return;
+
+ if (login_task_.get()) {
+ // start-stream should go to login task
+ login_task_->IncomingStanza(pelStart, true);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ }
+ else {
+ // if not logging in, it's an error to see a start
+ SignalError(ERROR_XML, 0);
+ }
+}
+
+void
+XmppEngineImpl::IncomingStanza(const XmlElement * stanza) {
+ if (HasError() || raised_reset_)
+ return;
+
+ if (stanza->Name() == QN_STREAM_ERROR) {
+ // Explicit XMPP stream error
+ SignalStreamError(stanza);
+ } else if (login_task_.get()) {
+ // Handle login handshake
+ login_task_->IncomingStanza(stanza, false);
+ if (login_task_->IsDone())
+ login_task_.reset();
+ } else if (HandleIqResponse(stanza)) {
+ // iq is handled by above call
+ } else {
+ // give every "peek" handler a shot at all stanzas
+ for (size_t i = 0; i < stanza_handlers_[HL_PEEK]->size(); i += 1) {
+ (*stanza_handlers_[HL_PEEK])[i]->HandleStanza(stanza);
+ }
+
+ // give other handlers a shot in precedence order, stopping after handled
+ for (int level = HL_SINGLE; level <= HL_ALL; level += 1) {
+ for (size_t i = 0; i < stanza_handlers_[level]->size(); i += 1) {
+ if ((*stanza_handlers_[level])[i]->HandleStanza(stanza))
+ goto Handled;
+ }
+ }
+
+ // If nobody wants to handle a stanza then send back an error.
+ // Only do this for IQ stanzas as messages should probably just be dropped
+ // and presence stanzas should certainly be dropped.
+ std::string type = stanza->Attr(QN_TYPE);
+ if (stanza->Name() == QN_IQ &&
+ !(type == "error" || type == "result")) {
+ SendStanzaError(stanza, XSE_FEATURE_NOT_IMPLEMENTED, STR_EMPTY);
+ }
+ }
+ Handled:
+ ; // handled - we're done
+}
+
+void
+XmppEngineImpl::IncomingEnd(bool isError) {
+ if (HasError() || raised_reset_)
+ return;
+
+ SignalError(isError ? ERROR_XML : ERROR_DOCUMENT_CLOSED, 0);
+}
+
+void
+XmppEngineImpl::InternalSendStart(const std::string & to) {
+ std::string hostname = tls_server_hostname_;
+ if (hostname.empty()) {
+ hostname = to;
+ }
+
+ // If not language is specified, the spec says use *
+ std::string lang = lang_;
+ if (lang.length() == 0)
+ lang = "*";
+
+ // send stream-beginning
+ // note, we put a \r\n at tne end fo the first line to cause non-XMPP
+ // line-oriented servers (e.g., Apache) to reveal themselves more quickly.
+ *output_ << "<stream:stream to=\"" << hostname << "\" "
+ << "xml:lang=\"" << lang << "\" "
+ << "version=\"1.0\" "
+ << "xmlns:stream=\"http://etherx.jabber.org/streams\" "
+ << "xmlns=\"jabber:client\">\r\n";
+}
+
+void
+XmppEngineImpl::InternalSendStanza(const XmlElement * element) {
+ // It should really never be necessary to set a FROM attribute on a stanza.
+ // It is implied by the bind on the stream and if you get it wrong
+ // (by flipping from/to on a message?) the server will close the stream.
+ ASSERT(!element->HasAttr(QN_FROM));
+
+ // TODO: consider caching the XmlPrinter
+ XmlPrinter::PrintXml(output_.get(), element,
+ XMPP_CLIENT_NAMESPACES, XMPP_CLIENT_NAMESPACES_LEN);
+}
+
+std::string
+XmppEngineImpl::ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted) {
+ return sasl_handler_->ChooseBestSaslMechanism(mechanisms, encrypted);
+}
+
+SaslMechanism *
+XmppEngineImpl::GetSaslMechanism(const std::string & name) {
+ return sasl_handler_->CreateSaslMechanism(name);
+}
+
+void
+XmppEngineImpl::SignalBound(const Jid & fullJid) {
+ if (state_ == STATE_OPENING) {
+ bound_jid_ = fullJid;
+ state_ = STATE_OPEN;
+ }
+}
+
+void
+XmppEngineImpl::SignalStreamError(const XmlElement * pelStreamError) {
+ if (state_ != STATE_CLOSED) {
+ stream_error_.reset(new XmlElement(*pelStreamError));
+ SignalError(ERROR_STREAM, 0);
+ }
+}
+
+void
+XmppEngineImpl::SignalError(Error errorCode, int subCode) {
+ if (state_ != STATE_CLOSED) {
+ error_code_ = errorCode;
+ subcode_ = subCode;
+ state_ = STATE_CLOSED;
+ }
+}
+
+bool
+XmppEngineImpl::HasError() {
+ return error_code_ != ERROR_NONE;
+}
+
+void
+XmppEngineImpl::StartTls(const std::string & domain) {
+ if (output_handler_) {
+ output_handler_->StartTls(
+ tls_server_domain_.empty() ? domain : tls_server_domain_);
+ encrypted_ = true;
+ }
+}
+
+XmppEngineImpl::EnterExit::EnterExit(XmppEngineImpl* engine)
+ : engine_(engine),
+ state_(engine->state_),
+ error_(engine->error_code_) {
+ engine->engine_entered_ += 1;
+}
+
+XmppEngineImpl::EnterExit::~EnterExit() {
+ XmppEngineImpl* engine = engine_;
+
+ engine->engine_entered_ -= 1;
+
+ bool closing = (engine->state_ != state_ &&
+ engine->state_ == STATE_CLOSED);
+ bool flushing = closing || (engine->engine_entered_ == 0);
+
+ if (engine->output_handler_ && flushing) {
+ std::string output = engine->output_->str();
+ if (output.length() > 0)
+ engine->output_handler_->WriteOutput(output.c_str(), output.length());
+ engine->output_->str("");
+
+ if (closing) {
+ engine->output_handler_->CloseConnection();
+ engine->output_handler_ = 0;
+ }
+ }
+
+ if (engine->engine_entered_)
+ return;
+
+ if (engine->raised_reset_) {
+ engine->stanzaParser_.Reset();
+ engine->raised_reset_ = false;
+ }
+
+ if (engine->session_handler_) {
+ if (engine->state_ != state_)
+ engine->session_handler_->OnStateChange(engine->state_);
+ // Note: Handling of OnStateChange(CLOSED) should allow for the
+ // deletion of the engine, so no members should be accessed
+ // after this line.
+ }
+}
+
+}
diff --git a/talk/xmpp/xmppengineimpl.h b/talk/xmpp/xmppengineimpl.h
new file mode 100644
index 0000000..e5d3dc6
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl.h
@@ -0,0 +1,278 @@
+/*
+ * 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 _xmppengineimpl_h_
+#define _xmppengineimpl_h_
+
+#include <sstream>
+#include <vector>
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+
+namespace buzz {
+
+class XmppLoginTask;
+class XmppEngine;
+class XmppIqEntry;
+class SaslHandler;
+class SaslMechanism;
+
+
+//! The XMPP connection engine.
+//! This engine implements the client side of the 'core' XMPP protocol.
+//! To use it, register an XmppOutputHandler to handle socket output
+//! and pass socket input to HandleInput. Then application code can
+//! set up the connection with a user, password, and other settings,
+//! and then call Connect() to initiate the connection.
+//! An application can listen for events and receive stanzas by
+//! registering an XmppStanzaHandler via AddStanzaHandler().
+class XmppEngineImpl : public XmppEngine {
+public:
+ XmppEngineImpl();
+ virtual ~XmppEngineImpl();
+
+ // SOCKET INPUT AND OUTPUT ------------------------------------------------
+
+ //! Registers the handler for socket output
+ virtual XmppReturnStatus SetOutputHandler(XmppOutputHandler *pxoh);
+
+ //! Provides socket input to the engine
+ virtual XmppReturnStatus HandleInput(const char * bytes, size_t len);
+
+ //! Advises the engine that the socket has closed
+ virtual XmppReturnStatus ConnectionClosed(int subcode);
+
+ // SESSION SETUP ---------------------------------------------------------
+
+ //! Indicates the (bare) JID for the user to use.
+ virtual XmppReturnStatus SetUser(const Jid & jid);
+
+ //! Get the login (bare) JID.
+ virtual const Jid & GetUser();
+
+ //! Indicates the autentication to use. Takes ownership of the object.
+ virtual XmppReturnStatus SetSaslHandler(SaslHandler * sasl_handler);
+
+ //! Sets whether TLS will be used within the connection (default true).
+ virtual XmppReturnStatus SetUseTls(bool useTls);
+
+ //! Sets an alternate domain from which we allows TLS certificates.
+ //! This is for use in the case where a we want to allow a proxy to
+ //! serve up its own certificate rather than one owned by the underlying
+ //! domain.
+ virtual XmppReturnStatus SetTlsServer(const std::string & proxy_hostname,
+ const std::string & proxy_domain);
+
+ //! Gets whether TLS will be used within the connection.
+ virtual bool GetUseTls();
+
+ //! Sets the request resource name, if any (optional).
+ //! Note that the resource name may be overridden by the server; after
+ //! binding, the actual resource name is available as part of FullJid().
+ virtual XmppReturnStatus SetRequestedResource(const std::string& resource);
+
+ //! Gets the request resource name.
+ virtual const std::string & GetRequestedResource();
+
+ //! Sets language
+ virtual void SetLanguage(const std::string & lang) {
+ lang_ = lang;
+ }
+
+ // SESSION MANAGEMENT ---------------------------------------------------
+
+ //! Set callback for state changes.
+ virtual XmppReturnStatus SetSessionHandler(XmppSessionHandler* handler);
+
+ //! Initiates the XMPP connection.
+ //! After supplying connection settings, call this once to initiate,
+ //! (optionally) encrypt, authenticate, and bind the connection.
+ virtual XmppReturnStatus Connect();
+
+ //! The current engine state.
+ virtual State GetState() { return state_; }
+
+ //! Returns true if the connection is encrypted (under TLS)
+ virtual bool IsEncrypted() { return encrypted_; }
+
+ //! The error code.
+ //! Consult this after XmppOutputHandler.OnClose().
+ virtual Error GetError(int *subcode) {
+ if (subcode) {
+ *subcode = subcode_;
+ }
+ return error_code_;
+ }
+
+ //! The stream:error stanza, when the error is XmppEngine::ERROR_STREAM.
+ //! Notice the stanza returned is owned by the XmppEngine and
+ //! is deleted when the engine is destroyed.
+ virtual const XmlElement * GetStreamError() { return stream_error_.get(); }
+
+ //! Closes down the connection.
+ //! Sends CloseConnection to output, and disconnects and registered
+ //! session handlers. After Disconnect completes, it is guaranteed
+ //! that no further callbacks will be made.
+ virtual XmppReturnStatus Disconnect();
+
+ // APPLICATION USE -------------------------------------------------------
+
+ //! Adds a listener for session events.
+ //! Stanza delivery is chained to session handlers; the first to
+ //! return 'true' is the last to get each stanza.
+ virtual XmppReturnStatus AddStanzaHandler(XmppStanzaHandler* handler,
+ XmppEngine::HandlerLevel level);
+
+ //! Removes a listener for session events.
+ virtual XmppReturnStatus RemoveStanzaHandler(XmppStanzaHandler* handler);
+
+ //! Sends a stanza to the server.
+ virtual XmppReturnStatus SendStanza(const XmlElement * pelStanza);
+
+ //! Sends raw text to the server
+ virtual XmppReturnStatus SendRaw(const std::string & text);
+
+ //! Sends an iq to the server, and registers a callback for the result.
+ //! Returns the cookie passed to the result handler.
+ virtual XmppReturnStatus SendIq(const XmlElement* pelStanza,
+ XmppIqHandler* iq_handler,
+ XmppIqCookie* cookie);
+
+ //! Unregisters an iq callback handler given its cookie.
+ //! No callback will come to this handler after it's unregistered.
+ virtual XmppReturnStatus RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler** iq_handler);
+
+ //! Forms and sends an error in response to the given stanza.
+ //! Swaps to and from, sets type to "error", and adds error information
+ //! based on the passed code. Text is optional and may be STR_EMPTY.
+ virtual XmppReturnStatus SendStanzaError(const XmlElement * pelOriginal,
+ XmppStanzaError code,
+ const std::string & text);
+
+ //! The fullly bound JID.
+ //! This JID is only valid after binding has succeeded. If the value
+ //! is JID_NULL, the binding has not succeeded.
+ virtual const Jid & FullJid() { return bound_jid_; }
+
+ //! The next unused iq id for this connection.
+ //! Call this when building iq stanzas, to ensure that each iq
+ //! gets its own unique id.
+ virtual std::string NextId();
+
+private:
+ friend class XmppLoginTask;
+ friend class XmppIqEntry;
+
+ void IncomingStanza(const XmlElement *pelStanza);
+ void IncomingStart(const XmlElement *pelStanza);
+ void IncomingEnd(bool isError);
+
+ void InternalSendStart(const std::string & domainName);
+ void InternalSendStanza(const XmlElement * pelStanza);
+ std::string ChooseBestSaslMechanism(const std::vector<std::string> & mechanisms, bool encrypted);
+ SaslMechanism * GetSaslMechanism(const std::string & name);
+ void SignalBound(const Jid & fullJid);
+ void SignalStreamError(const XmlElement * pelStreamError);
+ void SignalError(Error errorCode, int subCode);
+ bool HasError();
+ void DeleteIqCookies();
+ bool HandleIqResponse(const XmlElement * element);
+ void StartTls(const std::string & domain);
+ void RaiseReset() { raised_reset_ = true; }
+
+ class StanzaParseHandler : public XmppStanzaParseHandler {
+ public:
+ StanzaParseHandler(XmppEngineImpl * outer) : outer_(outer) {}
+ virtual ~StanzaParseHandler() {}
+ virtual void StartStream(const XmlElement * pelStream)
+ { outer_->IncomingStart(pelStream); }
+ virtual void Stanza(const XmlElement * pelStanza)
+ { outer_->IncomingStanza(pelStanza); }
+ virtual void EndStream()
+ { outer_->IncomingEnd(false); }
+ virtual void XmlError()
+ { outer_->IncomingEnd(true); }
+ private:
+ XmppEngineImpl * const outer_;
+ };
+
+ class EnterExit {
+ public:
+ EnterExit(XmppEngineImpl* engine);
+ ~EnterExit();
+ private:
+ XmppEngineImpl* engine_;
+ State state_;
+ Error error_;
+
+ };
+
+ friend class StanzaParseHandler;
+ friend class EnterExit;
+
+ StanzaParseHandler stanzaParseHandler_;
+ XmppStanzaParser stanzaParser_;
+
+
+ // state
+ int engine_entered_;
+ Jid user_jid_;
+ std::string password_;
+ std::string requested_resource_;
+ bool tls_needed_;
+ std::string tls_server_hostname_;
+ std::string tls_server_domain_;
+ talk_base::scoped_ptr<XmppLoginTask> login_task_;
+ std::string lang_;
+
+ int next_id_;
+ Jid bound_jid_;
+ State state_;
+ bool encrypted_;
+ Error error_code_;
+ int subcode_;
+ talk_base::scoped_ptr<XmlElement> stream_error_;
+ bool raised_reset_;
+ XmppOutputHandler* output_handler_;
+ XmppSessionHandler* session_handler_;
+
+ typedef std::vector<XmppStanzaHandler*> StanzaHandlerVector;
+ talk_base::scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT];
+
+ typedef std::vector<XmppIqEntry*> IqEntryVector;
+ talk_base::scoped_ptr<IqEntryVector> iq_entries_;
+
+ talk_base::scoped_ptr<SaslHandler> sasl_handler_;
+
+ talk_base::scoped_ptr<std::stringstream> output_;
+};
+
+}
+
+
+#endif
diff --git a/talk/xmpp/xmppengineimpl_iq.cc b/talk/xmpp/xmppengineimpl_iq.cc
new file mode 100644
index 0000000..5834b90
--- /dev/null
+++ b/talk/xmpp/xmppengineimpl_iq.cc
@@ -0,0 +1,277 @@
+/*
+ * 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 <vector>
+#include <algorithm>
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/constants.h"
+
+namespace buzz {
+
+class XmppIqEntry {
+ XmppIqEntry(const std::string & id, const std::string & to,
+ XmppEngine * pxce, XmppIqHandler * iq_handler) :
+ id_(id),
+ to_(to),
+ engine_(pxce),
+ iq_handler_(iq_handler) {
+ }
+
+private:
+ friend class XmppEngineImpl;
+
+ const std::string id_;
+ const std::string to_;
+ XmppEngine * const engine_;
+ XmppIqHandler * const iq_handler_;
+};
+
+
+XmppReturnStatus
+XmppEngineImpl::SendIq(const XmlElement * element, XmppIqHandler * iq_handler,
+ XmppIqCookie* cookie) {
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+ if (NULL == iq_handler)
+ return XMPP_RETURN_BADARGUMENT;
+ if (!element || element->Name() != QN_IQ)
+ return XMPP_RETURN_BADARGUMENT;
+
+ const std::string& type = element->Attr(QN_TYPE);
+ if (type != "get" && type != "set")
+ return XMPP_RETURN_BADARGUMENT;
+
+ if (!element->HasAttr(QN_ID))
+ return XMPP_RETURN_BADARGUMENT;
+ const std::string& id = element->Attr(QN_ID);
+
+ XmppIqEntry * iq_entry = new XmppIqEntry(id,
+ element->Attr(QN_TO),
+ this, iq_handler);
+ iq_entries_->push_back(iq_entry);
+ SendStanza(element);
+
+ if (cookie)
+ *cookie = iq_entry;
+
+ return XMPP_RETURN_OK;
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::RemoveIqHandler(XmppIqCookie cookie,
+ XmppIqHandler ** iq_handler) {
+
+ std::vector<XmppIqEntry*, std::allocator<XmppIqEntry*> >::iterator pos;
+
+ pos = std::find(iq_entries_->begin(),
+ iq_entries_->end(),
+ reinterpret_cast<XmppIqEntry*>(cookie));
+
+ if (pos == iq_entries_->end())
+ return XMPP_RETURN_BADARGUMENT;
+
+ XmppIqEntry* entry = *pos;
+ iq_entries_->erase(pos);
+ if (iq_handler)
+ *iq_handler = entry->iq_handler_;
+ delete entry;
+
+ return XMPP_RETURN_OK;
+}
+
+void
+XmppEngineImpl::DeleteIqCookies() {
+ for (size_t i = 0; i < iq_entries_->size(); i += 1) {
+ XmppIqEntry * iq_entry_ = (*iq_entries_)[i];
+ (*iq_entries_)[i] = NULL;
+ delete iq_entry_;
+ }
+ iq_entries_->clear();
+}
+
+static void
+AecImpl(XmlElement * error_element, const QName & name,
+ const char * type, const char * code) {
+ error_element->AddElement(new XmlElement(QN_ERROR));
+ error_element->AddAttr(QN_CODE, code, 1);
+ error_element->AddAttr(QN_TYPE, type, 1);
+ error_element->AddElement(new XmlElement(name, true), 1);
+}
+
+
+static void
+AddErrorCode(XmlElement * error_element, XmppStanzaError code) {
+ switch (code) {
+ case XSE_BAD_REQUEST:
+ AecImpl(error_element, QN_STANZA_BAD_REQUEST, "modify", "400");
+ break;
+ case XSE_CONFLICT:
+ AecImpl(error_element, QN_STANZA_CONFLICT, "cancel", "409");
+ break;
+ case XSE_FEATURE_NOT_IMPLEMENTED:
+ AecImpl(error_element, QN_STANZA_FEATURE_NOT_IMPLEMENTED,
+ "cancel", "501");
+ break;
+ case XSE_FORBIDDEN:
+ AecImpl(error_element, QN_STANZA_FORBIDDEN, "auth", "403");
+ break;
+ case XSE_GONE:
+ AecImpl(error_element, QN_STANZA_GONE, "modify", "302");
+ break;
+ case XSE_INTERNAL_SERVER_ERROR:
+ AecImpl(error_element, QN_STANZA_INTERNAL_SERVER_ERROR, "wait", "500");
+ break;
+ case XSE_ITEM_NOT_FOUND:
+ AecImpl(error_element, QN_STANZA_ITEM_NOT_FOUND, "cancel", "404");
+ break;
+ case XSE_JID_MALFORMED:
+ AecImpl(error_element, QN_STANZA_JID_MALFORMED, "modify", "400");
+ break;
+ case XSE_NOT_ACCEPTABLE:
+ AecImpl(error_element, QN_STANZA_NOT_ACCEPTABLE, "cancel", "406");
+ break;
+ case XSE_NOT_ALLOWED:
+ AecImpl(error_element, QN_STANZA_NOT_ALLOWED, "cancel", "405");
+ break;
+ case XSE_PAYMENT_REQUIRED:
+ AecImpl(error_element, QN_STANZA_PAYMENT_REQUIRED, "auth", "402");
+ break;
+ case XSE_RECIPIENT_UNAVAILABLE:
+ AecImpl(error_element, QN_STANZA_RECIPIENT_UNAVAILABLE, "wait", "404");
+ break;
+ case XSE_REDIRECT:
+ AecImpl(error_element, QN_STANZA_REDIRECT, "modify", "302");
+ break;
+ case XSE_REGISTRATION_REQUIRED:
+ AecImpl(error_element, QN_STANZA_REGISTRATION_REQUIRED, "auth", "407");
+ break;
+ case XSE_SERVER_NOT_FOUND:
+ AecImpl(error_element, QN_STANZA_REMOTE_SERVER_NOT_FOUND,
+ "cancel", "404");
+ break;
+ case XSE_SERVER_TIMEOUT:
+ AecImpl(error_element, QN_STANZA_REMOTE_SERVER_TIMEOUT, "wait", "502");
+ break;
+ case XSE_RESOURCE_CONSTRAINT:
+ AecImpl(error_element, QN_STANZA_RESOURCE_CONSTRAINT, "wait", "500");
+ break;
+ case XSE_SERVICE_UNAVAILABLE:
+ AecImpl(error_element, QN_STANZA_SERVICE_UNAVAILABLE, "cancel", "503");
+ break;
+ case XSE_SUBSCRIPTION_REQUIRED:
+ AecImpl(error_element, QN_STANZA_SUBSCRIPTION_REQUIRED, "auth", "407");
+ break;
+ case XSE_UNDEFINED_CONDITION:
+ AecImpl(error_element, QN_STANZA_UNDEFINED_CONDITION, "wait", "500");
+ break;
+ case XSE_UNEXPECTED_REQUEST:
+ AecImpl(error_element, QN_STANZA_UNEXPECTED_REQUEST, "wait", "400");
+ break;
+ }
+}
+
+
+XmppReturnStatus
+XmppEngineImpl::SendStanzaError(const XmlElement * element_original,
+ XmppStanzaError code,
+ const std::string & text) {
+
+ if (state_ == STATE_CLOSED)
+ return XMPP_RETURN_BADSTATE;
+
+ XmlElement error_element(element_original->Name());
+ error_element.AddAttr(QN_TYPE, "error");
+
+ // copy attrs, copy 'from' to 'to' and strip 'from'
+ for (const XmlAttr * attribute = element_original->FirstAttr();
+ attribute; attribute = attribute->NextAttr()) {
+ QName name = attribute->Name();
+ if (name == QN_TO)
+ continue; // no need to put a from attr. Server will stamp stanza
+ else if (name == QN_FROM)
+ name = QN_TO;
+ else if (name == QN_TYPE)
+ continue;
+ error_element.AddAttr(name, attribute->Value());
+ }
+
+ // copy children
+ for (const XmlChild * child = element_original->FirstChild();
+ child;
+ child = child->NextChild()) {
+ if (child->IsText()) {
+ error_element.AddText(child->AsText()->Text());
+ } else {
+ error_element.AddElement(new XmlElement(*(child->AsElement())));
+ }
+ }
+
+ // add error information
+ AddErrorCode(&error_element, code);
+ if (text != STR_EMPTY) {
+ XmlElement * text_element = new XmlElement(QN_STANZA_TEXT, true);
+ text_element->AddText(text);
+ error_element.AddElement(text_element);
+ }
+
+ SendStanza(&error_element);
+
+ return XMPP_RETURN_OK;
+}
+
+
+bool
+XmppEngineImpl::HandleIqResponse(const XmlElement * element) {
+ if (iq_entries_->empty())
+ return false;
+ if (element->Name() != QN_IQ)
+ return false;
+ std::string type = element->Attr(QN_TYPE);
+ if (type != "result" && type != "error")
+ return false;
+ if (!element->HasAttr(QN_ID))
+ return false;
+ std::string id = element->Attr(QN_ID);
+ std::string from = element->Attr(QN_FROM);
+
+ for (std::vector<XmppIqEntry *>::iterator it = iq_entries_->begin();
+ it != iq_entries_->end(); it += 1) {
+ XmppIqEntry * iq_entry = *it;
+ if (iq_entry->id_ == id && iq_entry->to_ == from) {
+ iq_entries_->erase(it);
+ iq_entry->iq_handler_->IqResponse(iq_entry, element);
+ delete iq_entry;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.cc b/talk/xmpp/xmpplogintask.cc
new file mode 100644
index 0000000..537818c
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.cc
@@ -0,0 +1,380 @@
+/*
+ * 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 <iostream>
+#include <string>
+#include <vector>
+#include "talk/base/base64.h"
+#include "talk/base/common.h"
+#include "talk/xmllite/xmlelement.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/saslmechanism.h"
+#include "talk/xmpp/xmppengineimpl.h"
+#include "talk/xmpp/xmpplogintask.h"
+
+using talk_base::ConstantLabel;
+
+namespace buzz {
+
+#ifdef _DEBUG
+const ConstantLabel XmppLoginTask::LOGINTASK_STATES[] = {
+ KLABEL(LOGINSTATE_INIT),
+ KLABEL(LOGINSTATE_STREAMSTART_SENT),
+ KLABEL(LOGINSTATE_STARTED_XMPP),
+ KLABEL(LOGINSTATE_TLS_INIT),
+ KLABEL(LOGINSTATE_AUTH_INIT),
+ KLABEL(LOGINSTATE_BIND_INIT),
+ KLABEL(LOGINSTATE_TLS_REQUESTED),
+ KLABEL(LOGINSTATE_SASL_RUNNING),
+ KLABEL(LOGINSTATE_BIND_REQUESTED),
+ KLABEL(LOGINSTATE_SESSION_REQUESTED),
+ KLABEL(LOGINSTATE_DONE),
+ LASTLABEL
+};
+#endif // _DEBUG
+
+XmppLoginTask::XmppLoginTask(XmppEngineImpl * pctx) :
+ pctx_(pctx),
+ authNeeded_(true),
+ state_(LOGINSTATE_INIT),
+ pelStanza_(NULL),
+ isStart_(false),
+ iqId_(STR_EMPTY),
+ pelFeatures_(NULL),
+ fullJid_(STR_EMPTY),
+ streamId_(STR_EMPTY),
+ pvecQueuedStanzas_(new std::vector<XmlElement *>()),
+ sasl_mech_(NULL) {
+}
+
+XmppLoginTask::~XmppLoginTask() {
+ for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1)
+ delete (*pvecQueuedStanzas_)[i];
+}
+
+void
+XmppLoginTask::IncomingStanza(const XmlElement *element, bool isStart) {
+ pelStanza_ = element;
+ isStart_ = isStart;
+ Advance();
+ pelStanza_ = NULL;
+ isStart_ = false;
+}
+
+const XmlElement *
+XmppLoginTask::NextStanza() {
+ const XmlElement * result = pelStanza_;
+ pelStanza_ = NULL;
+ return result;
+}
+
+bool
+XmppLoginTask::Advance() {
+
+ for (;;) {
+
+ const XmlElement * element = NULL;
+
+#if _DEBUG
+ LOG(LS_VERBOSE) << "XmppLoginTask::Advance - "
+ << talk_base::ErrorName(state_, LOGINTASK_STATES);
+#endif // _DEBUG
+
+ switch (state_) {
+
+ case LOGINSTATE_INIT: {
+ pctx_->RaiseReset();
+ pelFeatures_.reset(NULL);
+
+ // The proper domain to verify against is the real underlying
+ // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
+ // also allows matching against a proxy domain instead, if it is told
+ // to do so - see the implementation of XmppEngineImpl::StartTls and
+ // XmppEngine::SetTlsServerDomain to see how you can use that feature
+ pctx_->InternalSendStart(pctx_->user_jid_.domain());
+ state_ = LOGINSTATE_STREAMSTART_SENT;
+ break;
+ }
+
+ case LOGINSTATE_STREAMSTART_SENT: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (!isStart_ || !HandleStartStream(element))
+ return Failure(XmppEngine::ERROR_VERSION);
+
+ state_ = LOGINSTATE_STARTED_XMPP;
+ return true;
+ }
+
+ case LOGINSTATE_STARTED_XMPP: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (!HandleFeatures(element))
+ return Failure(XmppEngine::ERROR_VERSION);
+
+ // Use TLS if forced, or if available
+ if (pctx_->tls_needed_ || GetFeature(QN_TLS_STARTTLS) != NULL) {
+ state_ = LOGINSTATE_TLS_INIT;
+ continue;
+ }
+
+ if (authNeeded_) {
+ state_ = LOGINSTATE_AUTH_INIT;
+ continue;
+ }
+
+ state_ = LOGINSTATE_BIND_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_TLS_INIT: {
+ const XmlElement * pelTls = GetFeature(QN_TLS_STARTTLS);
+ if (!pelTls)
+ return Failure(XmppEngine::ERROR_TLS);
+
+ XmlElement el(QN_TLS_STARTTLS, true);
+ pctx_->InternalSendStanza(&el);
+ state_ = LOGINSTATE_TLS_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_TLS_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name() != QN_TLS_PROCEED)
+ return Failure(XmppEngine::ERROR_TLS);
+
+ // The proper domain to verify against is the real underlying
+ // domain - i.e., the domain that owns the JID. Our XmppEngineImpl
+ // also allows matching against a proxy domain instead, if it is told
+ // to do so - see the implementation of XmppEngineImpl::StartTls and
+ // XmppEngine::SetTlsServerDomain to see how you can use that feature
+ pctx_->StartTls(pctx_->user_jid_.domain());
+ pctx_->tls_needed_ = false;
+ state_ = LOGINSTATE_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_AUTH_INIT: {
+ const XmlElement * pelSaslAuth = GetFeature(QN_SASL_MECHANISMS);
+ if (!pelSaslAuth) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // Collect together the SASL auth mechanisms presented by the server
+ std::vector<std::string> mechanisms;
+ for (const XmlElement * pelMech =
+ pelSaslAuth->FirstNamed(QN_SASL_MECHANISM);
+ pelMech;
+ pelMech = pelMech->NextNamed(QN_SASL_MECHANISM)) {
+
+ mechanisms.push_back(pelMech->BodyText());
+ }
+
+ // Given all the mechanisms, choose the best
+ std::string choice(pctx_->ChooseBestSaslMechanism(mechanisms, pctx_->IsEncrypted()));
+ if (choice.empty()) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // No recognized auth mechanism - that's an error
+ sasl_mech_.reset(pctx_->GetSaslMechanism(choice));
+ if (sasl_mech_.get() == NULL) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ // OK, let's start it.
+ XmlElement * auth = sasl_mech_->StartSaslAuth();
+ if (auth == NULL) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+
+ pctx_->InternalSendStanza(auth);
+ delete auth;
+ state_ = LOGINSTATE_SASL_RUNNING;
+ continue;
+ }
+
+ case LOGINSTATE_SASL_RUNNING: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name().Namespace() != NS_SASL)
+ return Failure(XmppEngine::ERROR_AUTH);
+ if (element->Name() == QN_SASL_CHALLENGE) {
+ XmlElement * response = sasl_mech_->HandleSaslChallenge(element);
+ if (response == NULL) {
+ return Failure(XmppEngine::ERROR_AUTH);
+ }
+ pctx_->InternalSendStanza(response);
+ delete response;
+ state_ = LOGINSTATE_SASL_RUNNING;
+ continue;
+ }
+ if (element->Name() != QN_SASL_SUCCESS) {
+ return Failure(XmppEngine::ERROR_UNAUTHORIZED);
+ }
+
+ // Authenticated!
+ authNeeded_ = false;
+ state_ = LOGINSTATE_INIT;
+ continue;
+ }
+
+ case LOGINSTATE_BIND_INIT: {
+ const XmlElement * pelBindFeature = GetFeature(QN_BIND_BIND);
+ const XmlElement * pelSessionFeature = GetFeature(QN_SESSION_SESSION);
+ if (!pelBindFeature || !pelSessionFeature)
+ return Failure(XmppEngine::ERROR_BIND);
+
+ XmlElement iq(QN_IQ);
+ iq.AddAttr(QN_TYPE, "set");
+
+ iqId_ = pctx_->NextId();
+ iq.AddAttr(QN_ID, iqId_);
+ iq.AddElement(new XmlElement(QN_BIND_BIND, true));
+
+ if (pctx_->requested_resource_ != STR_EMPTY) {
+ iq.AddElement(new XmlElement(QN_BIND_RESOURCE), 1);
+ iq.AddText(pctx_->requested_resource_, 2);
+ }
+ pctx_->InternalSendStanza(&iq);
+ state_ = LOGINSTATE_BIND_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_BIND_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+
+ if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+ element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+ return true;
+
+ if (element->Attr(QN_TYPE) != "result" || element->FirstElement() == NULL ||
+ element->FirstElement()->Name() != QN_BIND_BIND)
+ return Failure(XmppEngine::ERROR_BIND);
+
+ fullJid_ = Jid(element->FirstElement()->TextNamed(QN_BIND_JID));
+ if (!fullJid_.IsFull()) {
+ return Failure(XmppEngine::ERROR_BIND);
+ }
+
+ // now request session
+ XmlElement iq(QN_IQ);
+ iq.AddAttr(QN_TYPE, "set");
+
+ iqId_ = pctx_->NextId();
+ iq.AddAttr(QN_ID, iqId_);
+ iq.AddElement(new XmlElement(QN_SESSION_SESSION, true));
+ pctx_->InternalSendStanza(&iq);
+
+ state_ = LOGINSTATE_SESSION_REQUESTED;
+ continue;
+ }
+
+ case LOGINSTATE_SESSION_REQUESTED: {
+ if (NULL == (element = NextStanza()))
+ return true;
+ if (element->Name() != QN_IQ || element->Attr(QN_ID) != iqId_ ||
+ element->Attr(QN_TYPE) == "get" || element->Attr(QN_TYPE) == "set")
+ return false;
+
+ if (element->Attr(QN_TYPE) != "result")
+ return Failure(XmppEngine::ERROR_BIND);
+
+ pctx_->SignalBound(fullJid_);
+ FlushQueuedStanzas();
+ state_ = LOGINSTATE_DONE;
+ return true;
+ }
+
+ case LOGINSTATE_DONE:
+ return false;
+ }
+ }
+}
+
+bool
+XmppLoginTask::HandleStartStream(const XmlElement *element) {
+
+ if (element->Name() != QN_STREAM_STREAM)
+ return false;
+
+ if (element->Attr(QN_XMLNS) != "jabber:client")
+ return false;
+
+ if (element->Attr(QN_VERSION) != "1.0")
+ return false;
+
+ if (!element->HasAttr(QN_ID))
+ return false;
+
+ streamId_ = element->Attr(QN_ID);
+
+ return true;
+}
+
+bool
+XmppLoginTask::HandleFeatures(const XmlElement *element) {
+ if (element->Name() != QN_STREAM_FEATURES)
+ return false;
+
+ pelFeatures_.reset(new XmlElement(*element));
+ return true;
+}
+
+const XmlElement *
+XmppLoginTask::GetFeature(const QName & name) {
+ return pelFeatures_->FirstNamed(name);
+}
+
+bool
+XmppLoginTask::Failure(XmppEngine::Error reason) {
+ state_ = LOGINSTATE_DONE;
+ pctx_->SignalError(reason, 0);
+ return false;
+}
+
+void
+XmppLoginTask::OutgoingStanza(const XmlElement * element) {
+ XmlElement * pelCopy = new XmlElement(*element);
+ pvecQueuedStanzas_->push_back(pelCopy);
+}
+
+void
+XmppLoginTask::FlushQueuedStanzas() {
+ for (size_t i = 0; i < pvecQueuedStanzas_->size(); i += 1) {
+ pctx_->InternalSendStanza((*pvecQueuedStanzas_)[i]);
+ delete (*pvecQueuedStanzas_)[i];
+ }
+ pvecQueuedStanzas_->clear();
+}
+
+}
diff --git a/talk/xmpp/xmpplogintask.h b/talk/xmpp/xmpplogintask.h
new file mode 100644
index 0000000..a6507b5
--- /dev/null
+++ b/talk/xmpp/xmpplogintask.h
@@ -0,0 +1,98 @@
+/*
+ * 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 _logintask_h_
+#define _logintask_h_
+
+#include <string>
+#include "talk/base/logging.h"
+#include "talk/base/scoped_ptr.h"
+#include "talk/xmpp/jid.h"
+#include "talk/xmpp/xmppengine.h"
+
+namespace buzz {
+
+class XmlElement;
+class XmppEngineImpl;
+class SaslMechanism;
+
+
+class XmppLoginTask {
+
+public:
+ XmppLoginTask(XmppEngineImpl *pctx);
+ ~XmppLoginTask();
+
+ bool IsDone()
+ { return state_ == LOGINSTATE_DONE; }
+ void IncomingStanza(const XmlElement * element, bool isStart);
+ void OutgoingStanza(const XmlElement *element);
+
+private:
+ enum LoginTaskState {
+ LOGINSTATE_INIT = 0,
+ LOGINSTATE_STREAMSTART_SENT,
+ LOGINSTATE_STARTED_XMPP,
+ LOGINSTATE_TLS_INIT,
+ LOGINSTATE_AUTH_INIT,
+ LOGINSTATE_BIND_INIT,
+ LOGINSTATE_TLS_REQUESTED,
+ LOGINSTATE_SASL_RUNNING,
+ LOGINSTATE_BIND_REQUESTED,
+ LOGINSTATE_SESSION_REQUESTED,
+ LOGINSTATE_DONE,
+ };
+
+ const XmlElement * NextStanza();
+ bool Advance();
+ bool HandleStartStream(const XmlElement * element);
+ bool HandleFeatures(const XmlElement * element);
+ const XmlElement * GetFeature(const QName & name);
+ bool Failure(XmppEngine::Error reason);
+ void FlushQueuedStanzas();
+
+ XmppEngineImpl * pctx_;
+ bool authNeeded_;
+ LoginTaskState state_;
+ const XmlElement * pelStanza_;
+ bool isStart_;
+ std::string iqId_;
+ talk_base::scoped_ptr<XmlElement> pelFeatures_;
+ Jid fullJid_;
+ std::string streamId_;
+ talk_base::scoped_ptr<std::vector<XmlElement *> > pvecQueuedStanzas_;
+
+ talk_base::scoped_ptr<SaslMechanism> sasl_mech_;
+
+#ifdef _DEBUG
+ static const talk_base::ConstantLabel LOGINTASK_STATES[];
+#endif // _DEBUG
+};
+
+}
+
+#endif
diff --git a/talk/xmpp/xmppstanzaparser.cc b/talk/xmpp/xmppstanzaparser.cc
new file mode 100644
index 0000000..3aced15
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.cc
@@ -0,0 +1,105 @@
+/*
+ * 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 "talk/xmllite/xmlelement.h"
+#include "talk/base/common.h"
+#include "talk/xmpp/xmppstanzaparser.h"
+#include "talk/xmpp/constants.h"
+#ifdef EXPAT_RELATIVE_PATH
+#include "lib/expat.h"
+#else
+#include "third_party/expat/v2_0_1/Source/lib/expat.h"
+#endif
+
+namespace buzz {
+
+XmppStanzaParser::XmppStanzaParser(XmppStanzaParseHandler *psph) :
+ psph_(psph),
+ innerHandler_(this),
+ parser_(&innerHandler_),
+ depth_(0),
+ builder_() {
+}
+
+void
+XmppStanzaParser::Reset() {
+ parser_.Reset();
+ depth_ = 0;
+ builder_.Reset();
+}
+
+void
+XmppStanzaParser::IncomingStartElement(
+ XmlParseContext * pctx, const char * name, const char ** atts) {
+ if (depth_++ == 0) {
+ XmlElement * pelStream = XmlBuilder::BuildElement(pctx, name, atts);
+ if (pelStream == NULL) {
+ pctx->RaiseError(XML_ERROR_SYNTAX);
+ return;
+ }
+ psph_->StartStream(pelStream);
+ delete pelStream;
+ return;
+ }
+
+ builder_.StartElement(pctx, name, atts);
+}
+
+void
+XmppStanzaParser::IncomingCharacterData(
+ XmlParseContext * pctx, const char * text, int len) {
+ if (depth_ > 1) {
+ builder_.CharacterData(pctx, text, len);
+ }
+}
+
+void
+XmppStanzaParser::IncomingEndElement(
+ XmlParseContext * pctx, const char * name) {
+ if (--depth_ == 0) {
+ psph_->EndStream();
+ return;
+ }
+
+ builder_.EndElement(pctx, name);
+
+ if (depth_ == 1) {
+ XmlElement *element = builder_.CreateElement();
+ psph_->Stanza(element);
+ delete element;
+ }
+}
+
+void
+XmppStanzaParser::IncomingError(
+ XmlParseContext * pctx, XML_Error errCode) {
+ UNUSED(pctx);
+ UNUSED(errCode);
+ psph_->XmlError();
+}
+
+}
diff --git a/talk/xmpp/xmppstanzaparser.h b/talk/xmpp/xmppstanzaparser.h
new file mode 100644
index 0000000..c6f8b08
--- /dev/null
+++ b/talk/xmpp/xmppstanzaparser.h
@@ -0,0 +1,97 @@
+/*
+ * 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 _xmppstanzaparser_h_
+#define _xmppstanzaparser_h_
+
+#include "talk/xmllite/xmlparser.h"
+#include "talk/xmllite/xmlbuilder.h"
+
+
+namespace buzz {
+
+class XmlElement;
+
+class XmppStanzaParseHandler {
+public:
+ virtual ~XmppStanzaParseHandler() {}
+ virtual void StartStream(const XmlElement * pelStream) = 0;
+ virtual void Stanza(const XmlElement * pelStanza) = 0;
+ virtual void EndStream() = 0;
+ virtual void XmlError() = 0;
+};
+
+class XmppStanzaParser {
+public:
+ XmppStanzaParser(XmppStanzaParseHandler *psph);
+ bool Parse(const char * data, size_t len, bool isFinal)
+ { return parser_.Parse(data, len, isFinal); }
+ void Reset();
+
+private:
+ class ParseHandler : public XmlParseHandler {
+ public:
+ ParseHandler(XmppStanzaParser * outer) : outer_(outer) {}
+ virtual void StartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts)
+ { outer_->IncomingStartElement(pctx, name, atts); }
+ virtual void EndElement(XmlParseContext * pctx,
+ const char * name)
+ { outer_->IncomingEndElement(pctx, name); }
+ virtual void CharacterData(XmlParseContext * pctx,
+ const char * text, int len)
+ { outer_->IncomingCharacterData(pctx, text, len); }
+ virtual void Error(XmlParseContext * pctx,
+ XML_Error errCode)
+ { outer_->IncomingError(pctx, errCode); }
+ private:
+ XmppStanzaParser * const outer_;
+ };
+
+ friend class ParseHandler;
+
+ void IncomingStartElement(XmlParseContext * pctx,
+ const char * name, const char ** atts);
+ void IncomingEndElement(XmlParseContext * pctx,
+ const char * name);
+ void IncomingCharacterData(XmlParseContext * pctx,
+ const char * text, int len);
+ void IncomingError(XmlParseContext * pctx,
+ XML_Error errCode);
+
+ XmppStanzaParseHandler * psph_;
+ ParseHandler innerHandler_;
+ XmlParser parser_;
+ int depth_;
+ XmlBuilder builder_;
+
+ };
+
+
+}
+
+#endif
diff --git a/talk/xmpp/xmpptask.cc b/talk/xmpp/xmpptask.cc
new file mode 100644
index 0000000..bbd50e3
--- /dev/null
+++ b/talk/xmpp/xmpptask.cc
@@ -0,0 +1,175 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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/xmpptask.h"
+#include "talk/xmpp/xmppclient.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/xmpp/constants.h"
+#include "talk/xmpp/ratelimitmanager.h"
+
+namespace buzz {
+
+RateLimitManager task_rate_manager;
+
+XmppTask::XmppTask(TaskParent* parent, XmppEngine::HandlerLevel level)
+ : Task(parent), client_(NULL) {
+#ifdef _DEBUG
+ debug_force_timeout_ = false;
+#endif
+
+ XmppClient* client =
+ static_cast<XmppClient*>(parent->GetParent(XMPP_CLIENT_TASK_CODE));
+ client_ = client;
+ id_ = client->NextId();
+ client->AddXmppTask(this, level);
+ client->SignalDisconnected.connect(this, &XmppTask::OnDisconnect);
+}
+
+XmppTask::~XmppTask() {
+ StopImpl();
+}
+
+void XmppTask::StopImpl() {
+ while (NextStanza() != NULL) {}
+ if (client_) {
+ client_->RemoveXmppTask(this);
+ client_->SignalDisconnected.disconnect(this);
+ client_ = NULL;
+ }
+}
+
+XmppReturnStatus XmppTask::SendStanza(const XmlElement* stanza) {
+ if (client_ == NULL)
+ return XMPP_RETURN_BADSTATE;
+ return client_->SendStanza(stanza);
+}
+
+XmppReturnStatus XmppTask::SendStanzaError(const XmlElement* element_original,
+ XmppStanzaError code,
+ const std::string& text) {
+ if (client_ == NULL)
+ return XMPP_RETURN_BADSTATE;
+ return client_->SendStanzaError(element_original, code, text);
+}
+
+void XmppTask::Stop() {
+ StopImpl();
+ Task::Stop();
+}
+
+void XmppTask::OnDisconnect() {
+ Error();
+}
+
+void XmppTask::QueueStanza(const XmlElement* stanza) {
+#ifdef _DEBUG
+ if (debug_force_timeout_)
+ return;
+#endif
+
+ stanza_queue_.push_back(new XmlElement(*stanza));
+ Wake();
+}
+
+const XmlElement* XmppTask::NextStanza() {
+ XmlElement* result = NULL;
+ if (!stanza_queue_.empty()) {
+ result = stanza_queue_.front();
+ stanza_queue_.pop_front();
+ }
+ next_stanza_.reset(result);
+ return result;
+}
+
+XmlElement* XmppTask::MakeIq(const std::string& type,
+ const buzz::Jid& to,
+ const std::string& id) {
+ XmlElement* result = new XmlElement(QN_IQ);
+ if (!type.empty())
+ result->AddAttr(QN_TYPE, type);
+ if (to != JID_EMPTY)
+ result->AddAttr(QN_TO, to.Str());
+ if (!id.empty())
+ result->AddAttr(QN_ID, id);
+ return result;
+}
+
+XmlElement* XmppTask::MakeIqResult(const XmlElement * query) {
+ XmlElement* result = new XmlElement(QN_IQ);
+ result->AddAttr(QN_TYPE, STR_RESULT);
+ if (query->HasAttr(QN_FROM)) {
+ result->AddAttr(QN_TO, query->Attr(QN_FROM));
+ }
+ result->AddAttr(QN_ID, query->Attr(QN_ID));
+ return result;
+}
+
+bool XmppTask::MatchResponseIq(const XmlElement* stanza,
+ const Jid& to,
+ const std::string& id) {
+ if (stanza->Name() != QN_IQ)
+ return false;
+
+ if (stanza->Attr(QN_ID) != id)
+ return false;
+
+ Jid from(stanza->Attr(QN_FROM));
+ if (from == to)
+ return true;
+
+ // We address the server as "", check if we are doing so here.
+ if (to != JID_EMPTY)
+ return false;
+
+ // It is legal for the server to identify itself with "domain" or
+ // "myself@domain"
+ Jid me = client_->jid();
+ return (from == Jid(me.domain())) || (from == me.BareJid());
+}
+
+bool XmppTask::MatchRequestIq(const XmlElement* stanza,
+ const std::string& type,
+ const QName& qn) {
+ if (stanza->Name() != QN_IQ)
+ return false;
+
+ if (stanza->Attr(QN_TYPE) != type)
+ return false;
+
+ if (stanza->FirstNamed(qn) == NULL)
+ return false;
+
+ return true;
+}
+
+bool XmppTask::VerifyTaskRateLimit(const std::string task_name, int max_count,
+ int per_x_seconds) {
+ return task_rate_manager.VerifyRateLimit(task_name, max_count,
+ per_x_seconds);
+}
+
+}
diff --git a/talk/xmpp/xmpptask.h b/talk/xmpp/xmpptask.h
new file mode 100644
index 0000000..84e2fe0
--- /dev/null
+++ b/talk/xmpp/xmpptask.h
@@ -0,0 +1,130 @@
+/*
+ * libjingle
+ * Copyright 2004--2006, 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 _XMPPTASK_H_
+#define _XMPPTASK_H_
+
+#include <string>
+#include <deque>
+#include "talk/base/sigslot.h"
+#include "talk/xmpp/xmppengine.h"
+#include "talk/base/task.h"
+
+namespace buzz {
+
+/////////////////////////////////////////////////////////////////////
+//
+// XMPPTASK
+//
+/////////////////////////////////////////////////////////////////////
+//
+// See Task and XmppClient first.
+//
+// XmppTask is a task that is designed to go underneath XmppClient and be
+// useful there. It has a way of finding its XmppClient parent so you
+// can have it nested arbitrarily deep under an XmppClient and it can
+// still find the XMPP services.
+//
+// Tasks register themselves to listen to particular kinds of stanzas
+// that are sent out by the client. Rather than processing stanzas
+// right away, they should decide if they own the sent stanza,
+// and if so, queue it and Wake() the task, or if a stanza does not belong
+// to you, return false right away so the next XmppTask can take a crack.
+// This technique (synchronous recognize, but asynchronous processing)
+// allows you to have arbitrary logic for recognizing stanzas yet still,
+// for example, disconnect a client while processing a stanza -
+// without reentrancy problems.
+//
+/////////////////////////////////////////////////////////////////////
+
+class XmppClient;
+
+class XmppTask :
+ public talk_base::Task,
+ public XmppStanzaHandler,
+ public sigslot::has_slots<>
+{
+ public:
+ XmppTask(talk_base::TaskParent* parent,
+ XmppEngine::HandlerLevel level = XmppEngine::HL_NONE);
+ virtual ~XmppTask();
+
+ virtual XmppClient* GetClient() const { return client_; }
+ std::string task_id() const { return id_; }
+ void set_task_id(std::string id) { id_ = id; }
+
+#ifdef _DEBUG
+ void set_debug_force_timeout(const bool f) { debug_force_timeout_ = f; }
+#endif
+
+ protected:
+ friend class XmppClient;
+
+ XmppReturnStatus SendStanza(const XmlElement* stanza);
+ XmppReturnStatus SetResult(const std::string& code);
+ XmppReturnStatus SendStanzaError(const XmlElement* element_original,
+ XmppStanzaError code,
+ const std::string& text);
+
+ virtual void Stop();
+ virtual bool HandleStanza(const XmlElement* stanza) { return false; }
+ virtual void OnDisconnect();
+ virtual int ProcessReponse() { return STATE_DONE; }
+
+ virtual void QueueStanza(const XmlElement* stanza);
+ const XmlElement* NextStanza();
+
+ bool MatchResponseIq(const XmlElement* stanza, const Jid& to,
+ const std::string& task_id);
+
+ static bool MatchRequestIq(const XmlElement* stanza, const std::string& type,
+ const QName& qn);
+ static XmlElement *MakeIqResult(const XmlElement* query);
+ static XmlElement *MakeIq(const std::string& type,
+ const Jid& to, const std::string& task_id);
+
+ // Returns true if the task is under the specified rate limit and updates the
+ // rate limit accordingly
+ bool VerifyTaskRateLimit(const std::string task_name, int max_count,
+ int per_x_seconds);
+
+private:
+ void StopImpl();
+
+ XmppClient* client_;
+ std::deque<XmlElement*> stanza_queue_;
+ talk_base::scoped_ptr<XmlElement> next_stanza_;
+ std::string id_;
+
+#ifdef _DEBUG
+ bool debug_force_timeout_;
+#endif
+};
+
+}
+
+#endif