diff options
author | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-14 20:20:41 +0000 |
---|---|---|
committer | nick@chromium.org <nick@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-09-14 20:20:41 +0000 |
commit | 75961254aa04f575b79b1dd0be4439484da385e0 (patch) | |
tree | df7586311bf1207f520b4b8f5ce824d76d6cb7a5 | |
parent | 2be2c01334383d74703dc88ecb67fae96a9124f9 (diff) | |
download | chromium_src-75961254aa04f575b79b1dd0be4439484da385e0.zip chromium_src-75961254aa04f575b79b1dd0be4439484da385e0.tar.gz chromium_src-75961254aa04f575b79b1dd0be4439484da385e0.tar.bz2 |
Add libjingle version 0.4.0, plus some patches
that are described in README.chromium and
a .diff file, under review separately.
I removed some of the weightier
autoconf/configure scripts. This brought the
size of the library from 3.8MB to about 2.3MB.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/199090
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@26154 0039d316-1c4b-4281-b951-d872f2087c98
264 files changed, 54701 insertions, 0 deletions
diff --git a/third_party/libjingle/README.chromium b/third_party/libjingle/README.chromium new file mode 100644 index 0000000..8437d1c --- /dev/null +++ b/third_party/libjingle/README.chromium @@ -0,0 +1,33 @@ +URL: http://code.google.com/p/libjingle/ +Version: 2.7.0 +License: BSD +License File: COPYING + +Description: + Libjingle provides xmpp support to the sync code, which + lives at chrome/browser/sync/notifier. + + Libjingle is distributed under a Berkeley-style license detailed in + files/COPYING. + +Local Modifications: + Applied changes per the patchfile mods-since-v0_4_0.diff. That + includes the following items. + * Added support to specify the token_service in the auth stanza. + * Added support for non-gaia ids associated with gaia accounts. + * A bug fix in the task code to prevent infinite looping of aborted + tasks. + * Removed the definition of DISALLOW_EVIL_CONSTRUCTORS from common.h + * Introduction of #undef ETIMEDOUT to get around a conflict versus + pthreads.h. + * We use an "overrides" directory for some headers, for example, + scoped_ptr.h and basic_types.h, to avoid a duplicate functionality. + The overrides typically pull an implementation from src/base, and + then supplement this with any additional defines not found + in Chromium. + * Edits to config.h, changing some #defines. + * Removed some scripts and build files not needed by Chromium: aclocal.m4, + config.guess, config.h.in, config.sub, configure, depcomp, install-sh, + ltmain.sh, missing. + * Removed Makefile.in files, *.sln, *.vcproj. + * Removed "examples", "thirdparty", and "sessions" directories.
\ No newline at end of file diff --git a/third_party/libjingle/files/AUTHORS b/third_party/libjingle/files/AUTHORS new file mode 100644 index 0000000..e491a9e --- /dev/null +++ b/third_party/libjingle/files/AUTHORS @@ -0,0 +1 @@ +Google Inc. diff --git a/third_party/libjingle/files/COPYING b/third_party/libjingle/files/COPYING new file mode 100644 index 0000000..d11f105 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/ChangeLog b/third_party/libjingle/files/ChangeLog new file mode 100644 index 0000000..ce00334 --- /dev/null +++ b/third_party/libjingle/files/ChangeLog @@ -0,0 +1,13 @@ +Libjingle + +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/third_party/libjingle/files/DOCUMENTATION b/third_party/libjingle/files/DOCUMENTATION new file mode 100644 index 0000000..7739aef --- /dev/null +++ b/third_party/libjingle/files/DOCUMENTATION @@ -0,0 +1 @@ +Documentation for libjingle can be found at http://code.google.com/apis/talk/libjingle/ diff --git a/third_party/libjingle/files/INSTALL b/third_party/libjingle/files/INSTALL new file mode 100644 index 0000000..a4b3414 --- /dev/null +++ b/third_party/libjingle/files/INSTALL @@ -0,0 +1,229 @@ +Copyright 1994, 1995, 1996, 1999, 2000, 2001, 2002 Free Software +Foundation, Inc. + + This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + + These are generic installation instructions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. (Caching is +disabled by default to prevent problems with accidental use of stale +cache files.) + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You only need +`configure.ac' if you want to change it or regenerate `configure' using +a newer version of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. If you're + using `csh' on an old version of System V, you might need to type + `sh ./configure' instead to prevent `csh' from trying to execute + `configure' itself. + + Running `configure' takes awhile. While running, it prints some + messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c89 CFLAGS=-O2 LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you must use a version of `make' that +supports the `VPATH' variable, such as GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + If you have to use a `make' that does not support the `VPATH' +variable, you have to compile the package for one architecture at a +time in the source code directory. After you have installed the +package for one architecture, use `make distclean' before reconfiguring +for another architecture. + +Installation Names +================== + + By default, `make install' will install the package's files in +`/usr/local/bin', `/usr/local/man', etc. You can specify an +installation prefix other than `/usr/local' by giving `configure' the +option `--prefix=PATH'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +give `configure' the option `--exec-prefix=PATH', the package will use +PATH as the prefix for installing programs and libraries. +Documentation and other data files will still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=PATH' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the `--target=TYPE' option to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +will cause the specified gcc to be used as the C compiler (unless it is +overridden in the site shell script). + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/third_party/libjingle/files/Makefile.am b/third_party/libjingle/files/Makefile.am new file mode 100644 index 0000000..d4cf1cb --- /dev/null +++ b/third_party/libjingle/files/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS=talk + +noinst_HEADERS = config.h + +EXTRA_DIST=README.win DOCUMENTATION + +dist-hook: + sed -i -f talk/sanitize.sed `find $(distdir) -type f` diff --git a/third_party/libjingle/files/NEWS b/third_party/libjingle/files/NEWS new file mode 100644 index 0000000..bccf886 --- /dev/null +++ b/third_party/libjingle/files/NEWS @@ -0,0 +1,15 @@ +0.4.0 +* File transfer support +* Protocol updates +* Proxy detection +* Relay server support +* Many other assorted changes + +0.2.0 +* Windows build files +* TCP-like reliable stream support +* pcp example application using pseudo-TCP streams +* Support for TLS login on both example applications + +0.1.0 +* Initial source release diff --git a/third_party/libjingle/files/README b/third_party/libjingle/files/README new file mode 100644 index 0000000..5861c98 --- /dev/null +++ b/third_party/libjingle/files/README @@ -0,0 +1,57 @@ +Libjingle + +Libjingle is a set of components provided by Google to interoperate with Google +Talk's peer-to-peer and voice capabilities. This package will create several +static libraries you may link to your project 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 +|-third_party - Components that aren't ours + |-mediastreamer - Media components for dealing with sound hardware and + | voice codecs +|-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. + +The xmllite component of libjingle depends on expat. You can download expat +from http://expat.sourceforge.net/. + +mediastreamer, the media components used by the example applications depend on +the oRTP and iLBC components from linphone, which can be found at +http://www.linphone.org. Linphone, in turn depends on GLib, which can be found +at http://www.gtk.org. This GLib dependency should be removed in future +releases. + +Building Libjingle + +Once the dependencies are installed, run ./configure. ./configure will return +an error if it failed to locate the proper dependencies. If ./configure +succeeds, run 'make' to build the components and examples. + +When the build is complete, you can run the call example from +talk/examples/call and the pcp example from talk/examples/pcp. + +Relay Server + +Libjingle will also build a relay server that may be used to relay traffic +when a direct peer-to-peer connection could not be established. The relay +server will build in talk/p2p/base/relayserver and will listen on UDP +ports 5000 and 5001. 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. + +STUN Server + +Lastly, Libjingle builds a STUN server which implements the STUN protocol for +Simple Traversal of UDP over NAT. The STUN server is built as +talk/p2p/base/stunserver and listens on UDP port 7000. See the Libjingle +Developer Guide at http://code.google.com/apis/talk/index.html for information +about configuring a client to use this STUN server. diff --git a/third_party/libjingle/files/README.win b/third_party/libjingle/files/README.win new file mode 100644 index 0000000..1f45b72 --- /dev/null +++ b/third_party/libjingle/files/README.win @@ -0,0 +1,24 @@ +1. Install Visual C++ Express 2005. It is free from this link: + http://msdn.microsoft.com/vstudio/express/visualc/ + +2. Install the platform SDK and integrate it into VC++ express + http://msdn.microsoft.com/vstudio/express/visualc/usingpsdk/ + +3. Download and install binary package for expat: + http://sourceforge.net/project/showfiles.php?group_id=10127&package_id=11277 + +4. Update the Visual C++ directories in the Projects and Solutions section in the Options dialog box + Library files: C:\expat-VERSION\StaticLibs + Include files: C:\expat-VERSION\Source\Lib + where VERSION is the version of expat you've downoaded + +5. Unzip the libjingle files and open the solution. + +6. If you wish to build the call example with GIPS Voice Engine Lite, download Voice Engine Lite from http://developer.globalipsound.com + +7. Extract the Interface and Library directories from the Voice Engine Lite zip file into talk\third_party\gips + +8. Open talk\third_party\gips\expiration.h and set the GIPS_EXPIRATION #defines to the expiration date provided by GIPS and remove the #error directive + +9. Build the solution + diff --git a/third_party/libjingle/files/config.h b/third_party/libjingle/files/config.h new file mode 100644 index 0000000..ddc8182 --- /dev/null +++ b/third_party/libjingle/files/config.h @@ -0,0 +1,113 @@ +/* config.h. Generated by configure. */ +/* config.h.in. Generated from configure.ac by autoheader. */ + +/* Chat archving */ +#define FEATURE_ENABLE_CHAT_ARCHIVING 1 + +/* Enable SSL */ +#define FEATURE_ENABLE_SSL 1 + +/* voice mail */ +#define FEATURE_ENABLE_VOICEMAIL 1 + +/* Define to 1 if you have the <alsa/asoundlib.h> header file. */ +/* #undef HAVE_ALSA_ASOUNDLIB_H */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Have GIPS Voice Engine */ +/* #undef HAVE_GIPS */ + +/* Glib is required for oRTP code */ +/* #undef HAVE_GLIB */ + +/* Defined when we have ilbc codec lib */ +/* #undef HAVE_ILBC */ + +/* Define to 1 if you have the <iLBC_decode.h> header file. */ +/* #undef HAVE_ILBC_DECODE_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* oRTP provides RTP supprt */ +/* #undef HAVE_ORTP */ + +/* has speex */ +/* #undef HAVE_SPEEX */ + +/* Define to 1 if you have the <speex.h> header file. */ +/* #undef HAVE_SPEEX_H */ + +/* Define to 1 if you have the <speex/speex.h> header file. */ +/* #undef HAVE_SPEEX_SPEEX_H */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Building on Linux */ +/* #undef LINUX */ + +/* Logging */ +#define LOGGING 1 + +/* Building on OSX */ +/* #undef OSX */ + +/* Name of package */ +#define PACKAGE "libjingle" + +/* Define to the address where bug reports for this package should be sent. */ +#define PACKAGE_BUGREPORT "google-talk-open@googlegroups.com" + +/* Define to the full name of this package. */ +#define PACKAGE_NAME "libjingle" + +/* Define to the full name and version of this package. */ +#define PACKAGE_STRING "libjingle 0.4.0" + +/* Define to the one symbol short name of this package. */ +#define PACKAGE_TARNAME "libjingle" + +/* Define to the version of this package. */ +#define PACKAGE_VERSION "0.4.0" + +/* If we're using configure, we're on POSIX */ +#define POSIX 1 + +/* Build as a production build */ +#define PRODUCTION 1 + +/* Build as a production build */ +#define PRODUCTION_BUILD 1 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +#define VERSION "0.4.0" + +/* Defined when alsa support is enabled */ +/* #undef __ALSA_ENABLED__ */ diff --git a/third_party/libjingle/files/configure.ac b/third_party/libjingle/files/configure.ac new file mode 100644 index 0000000..b3daac4 --- /dev/null +++ b/third_party/libjingle/files/configure.ac @@ -0,0 +1,182 @@ +AC_INIT([libjingle], [0.4.0], [google-talk-open@googlegroups.com]) +AC_CANONICAL_SYSTEM +AM_CONFIG_HEADER(config.h) +AM_INIT_AUTOMAKE([dist-zip]) +AC_PROG_CC +AC_PROG_CXX +AM_PROG_LIBTOOL +LIBTOOL="$LIBTOOL --silent" +AC_PROG_INSTALL +AC_DEFINE(PRODUCTION_BUILD, 1, [Build as a production build]) +AC_DEFINE(PRODUCTION, 1, [Build as a production build]) +AC_DEFINE(POSIX, 1, [If we're using configure, we're on POSIX]) +AC_DEFINE(FEATURE_ENABLE_SSL, 1, [Enable SSL]) +AC_DEFINE(FEATURE_ENABLE_CHAT_ARCHIVING, 1, [Chat archving]) +AC_DEFINE(FEATURE_ENABLE_VOICEMAIL, 1, [voice mail]) +AC_DEFINE(LOGGING, 1, [Logging]) + +HAVE_EXPAT=no +AC_CHECK_LIB(expat, XML_ParserCreate, HAVE_EXPAT="yes") +if test "x$HAVE_EXPAT" = xyes ; then + EXPAT_LIBS="-lexpat" + AC_SUBST(EXPAT_LIBS) +else + AC_ERROR([Expat is required to build libjingle. You can get it from http://expat.sourceforge.net/]) +fi + +enable_phone=yes +if test `uname -s` = Linux ; then + AC_DEFINE(LINUX, 1, [Building on Linux]) + if test x`ls talk/third_party/gips/VoiceEngine_Linux_gcc.a` != x ; then + enable_gips=yes + MEDIA_LIBS=$PWD/talk/third_party/gips/VoiceEngine_Linux_gcc.a + fi +elif test `uname -s` = Darwin ; then + AC_DEFINE(OSX, 1, [Building on OSX]) + if test x`ls talk/third_party/gips/VoiceEngine_mac_gcc.a` != x ; then + enable_gips=yes + MEDIA_LIBS="$PWD/talk/third_party/gips/VoiceEngine_mac_gcc.a -framework CoreAudio -framework AudioToolbox" + fi +fi + +if test x$enable_gips = xyes ; then + AC_DEFINE(HAVE_GIPS, 1, [Have GIPS Voice Engine]) +elif test `uname -s` = Linux ; then + +AC_CHECK_HEADERS(alsa/asoundlib.h, + [AC_CHECK_LIB(asound, snd_pcm_open, + [ALSA_LIBS="-lasound" ; AC_DEFINE(__ALSA_ENABLED__,1,[Defined when alsa support is enabled]) ]) + ] +) +AC_SUBST(ALSA_LIBS) + +PKG_CHECK_MODULES(GLIB, glib-2.0 gmodule-2.0 gthread-2.0, enable_glib=yes, enable_glib=no) +if test x$enable_glib = xno; then + enable_phone=no + AC_MSG_NOTICE([GLib 2.0 is required to build libjingle. You can get it from http://www.gtk.org/]) +else + AC_SUBST(GLIB_CFLAGS) + AC_SUBST(GLIB_LIBS) + AC_DEFINE(HAVE_GLIB, 1, [Glib is required for oRTP code]) +fi + +PKG_CHECK_MODULES(ORTP, ortp >= 0.7.0, enable_ortp=yes, enable_ortp=no) +if test x$enable_ortp = xno ; then + enable_phone=no + AC_MSG_NOTICE([oRTP is required to build libjingle. You can get it from http://www.linphone.org/ortp/]) +else + AC_SUBST(ORTP_CFLAGS) + AC_SUBST(ORTP_LIBS) + AC_DEFINE(HAVE_ORTP, 1, [oRTP provides RTP supprt]) +fi + +MEDIA_LIBS="$PWD/talk/third_party/mediastreamer/libmediastreamer.la -lasound" + +dnl only accept speex>=1.1.6 or 1.0.5 (the versions that have speex_encode_int ) +AC_ARG_WITH( speex, + [ --with-speex Set prefix where speex lib can be found (ex:/usr, /usr/local) [default=/usr] ], + [ speex_prefix=${withval}],[ speex_prefix="/usr" ]) + +AC_CHECK_HEADERS(speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no) +],speex_found=no) + +if test "$speex_found" = "no" ; then +AC_CHECK_HEADERS(speex/speex.h,[AC_CHECK_LIB(speex,speex_encode_int,speex_found=yes,speex_found=no) +],speex_found=no) +fi + +if test "$speex_found" = "no" ; then +AC_MSG_WARN([Could not find a libspeex version that have the speex_encode_int() function. Please install libspeex=1.0.5 or libspeex>=1.1.6 from http://www.speex.org/]) +else +SPEEX_CFLAGS=" -I${speex_prefix}/include -I${speex_prefix}/include/speex" +SPEEX_LIBS="-L${speex_prefix}/lib -L${speex_prefix}/speex/lib -lspeex -lm" +CPPFLAGS_save=$CPPFLAGS +CPPFLAGS=$SPEEX_CFLAGS +LDFLAGS_save=$LDFLAGS +LDFLAGS=$SPEEX_LIBS +AC_DEFINE(HAVE_SPEEX,1,[has speex]) +fi + +AC_SUBST(SPEEX_CFLAGS) +AC_SUBST(SPEEX_LIBS) +CPPFLAGS+=$CPPFLAGS_save +LDFLAGS+=$LDFLAGS_save + +AC_ARG_WITH( ilbc, + [ --with-ilbc Set prefix where ilbc headers and libs can be found (ex:/usr, /usr/local, none to disable ilbc support) [default=/usr] ], + [ ilbc_prefix=${withval}],[ ilbc_prefix="/usr" ]) + +if test "$ilbc_prefix" = "none" ; then + AC_MSG_NOTICE([iLBC codec support disabled. ]) +else + ILBC_CFLAGS=" -I${ilbc_prefix}/include/ilbc" + ILBC_LIBS="-L${ilbc_prefix}/lib -lilbc -lm" + CPPFLAGS_save=$CPPFLAGS + CPPFLAGS=$ILBC_CFLAGS + LDFLAGS_save=$LDFLAGS + LDFLAGS=$ILBC_LIBS + AC_CHECK_HEADERS(iLBC_decode.h,[AC_CHECK_LIB(ilbc,iLBC_decode,ilbc_found=yes,ilbc_found=no) + ],ilbc_found=no) + + CPPFLAGS=$CPPFLAGS_save + LDFLAGS=$LDFLAGS_save + + if test "$ilbc_found" = "no" ; then + AC_MSG_WARN([Could not find ilbc headers or libs. Please install ilbc package from http://www.linphone.org if you want iLBC codec support in libjingle.]) + ILBC_CFLAGS= + ILBC_LIBS= + else + AC_DEFINE(HAVE_ILBC,1,[Defined when we have ilbc codec lib]) + AC_SUBST(ILBC_CFLAGS) + AC_SUBST(ILBC_LIBS) + fi +fi + +else + enable_phone=no +fi # Linux check + +AM_CONDITIONAL(GIPS, test x$enable_gips = xyes) +AM_CONDITIONAL(PHONE, test x$enable_phone = xyes) +AC_SUBST(MEDIA_LIBS) + +AC_OUTPUT([Makefile + talk/Makefile + talk/base/Makefile + talk/third_party/Makefile + talk/third_party/gips/Makefile + talk/third_party/mediastreamer/Makefile + talk/examples/Makefile + talk/examples/login/Makefile + talk/examples/call/Makefile + talk/examples/pcp/Makefile + talk/p2p/Makefile + talk/p2p/base/Makefile + talk/p2p/client/Makefile + talk/session/Makefile + talk/session/fileshare/Makefile + talk/session/tunnel/Makefile + talk/session/phone/Makefile + talk/xmllite/Makefile + talk/xmpp/Makefile + ]) + +echo +echo $PACKAGE $VERSION +echo + +if test x$enable_phone = xyes ; then + echo + echo Supported Examples: call pcp + echo Supported Codecs: + if test x$enable_gips = xyes ; then + echo GIPS: yes + else + echo Speex: $speex_found + echo iLBC: $ilbc_found + echo MULAW: yes + fi +else + echo Supported Examples: pcp +fi +echo diff --git a/third_party/libjingle/files/talk/Makefile.am b/third_party/libjingle/files/talk/Makefile.am new file mode 100644 index 0000000..dc6cc26 --- /dev/null +++ b/third_party/libjingle/files/talk/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS=base xmllite xmpp p2p session third_party examples + +EXTRA_DIST=libjingle.sln libjingle.vcproj diff --git a/third_party/libjingle/files/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h b/third_party/libjingle/files/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h new file mode 100644 index 0000000..6ff97a6 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/Makefile.am b/third_party/libjingle/files/talk/base/Makefile.am new file mode 100644 index 0000000..4a1e321 --- /dev/null +++ b/third_party/libjingle/files/talk/base/Makefile.am @@ -0,0 +1,152 @@ +libcricketbase_la_SOURCES = socketaddress.cc \ + time.cc \ + asyncudpsocket.cc \ + messagequeue.cc \ + thread.cc \ + physicalsocketserver.cc \ + bytebuffer.cc \ + asyncpacketsocket.cc \ + network.cc \ + asynctcpsocket.cc \ + socketadapters.cc \ + md5c.c \ + base64.cc \ + task.cc \ + taskrunner.cc \ + host.cc \ + proxydetect.cc \ + socketaddresspair.cc \ + stringencode.cc \ + stringdigest.cc \ + stringutils.cc \ + proxyinfo.cc \ + common.cc \ + logging.cc \ + stream.cc \ + ssladapter.cc \ + openssladapter.cc \ + helpers.cc \ + asynchttprequest.cc \ + firewallsocketserver.cc \ + httpcommon.cc \ + httpbase.cc \ + httpclient.cc \ + httpserver.cc \ + socketpool.cc \ + signalthread.cc \ + autodetectproxy.cc \ + urlencode.cc \ + pathutils.cc \ + fileutils.cc \ + unixfilesystem.cc \ + tarstream.cc \ + streamutils.cc \ + diskcache.cc \ + diskcachestd.cc + +libcrickettest_la_SOURCES = testclient.cc \ + natserver.cc \ + natsocketfactory.cc \ + nattypes.cc \ + virtualsocketserver.cc + +noinst_HEADERS = asyncfile.h \ + common.h \ + convert.h \ + asyncpacketsocket.h \ + socketfactory.h \ + asyncsocket.h \ + socket.h \ + asynctcpsocket.h \ + linked_ptr.h \ + asyncudpsocket.h \ + logging.h \ + socketserver.h \ + base64.h \ + md5.h \ + stl_decl.h \ + basicdefs.h \ + messagequeue.h \ + basictypes.h \ + stringdigest.h \ + stringencode.h \ + stringutils.h \ + bytebuffer.h \ + task.h \ + byteorder.h \ + taskrunner.h \ + criticalsection.h \ + network.h \ + thread.h \ + time.h \ + physicalsocketserver.h \ + proxyinfo.h \ + host.h \ + scoped_ptr.h \ + sigslot.h \ + winping.h \ + socketadapters.h \ + socketaddress.h \ + host.h \ + socketaddresspair.h \ + Equifax_Secure_Global_eBusiness_CA-1.h \ + stream.h \ + ssladapter.h \ + openssladapter.h \ + cryptstring.h \ + httpbase.h \ + httpclient.h \ + httpcommon.h \ + httpserver.h \ + httpcommon-inl.h \ + proxydetect.h \ + helpers.h \ + socketpool.h \ + asynchttprequest.h \ + signalthread.h \ + firewallsocketserver.h \ + diskcache.h \ + pathutils.h \ + socketstream.h \ + autodetectproxy.h \ + urlencode.h \ + fileutils.h \ + unixfilesystem.h \ + win32filesystem.h \ + tarstream.h \ + streamutils.h \ + diskcachestd.h \ + testclient.h \ + natserver.h \ + nattypes.h \ + natsocketfactory.h \ + virtualsocketserver.h \ + event.h + +AM_CXXFLAGS = -DPOSIX +DEFAULT_INCLUDES = -I$(top_srcdir) `pkg-config --cflags gtk+-2.0` + +noinst_LTLIBRARIES = libcricketbase.la libcrickettest.la +noinst_PROGRAMS = natserver nat_unittest virtualsocket_unittest + +natserver_SOURCES = natserver_main.cc +natserver_LDADD = libcrickettest.la libcricketbase.la -lpthread +nat_unittest_SOURCES = nat_unittest.cc +nat_unittest_LDADD = libcrickettest.la libcricketbase.la -lpthread +virtualsocket_unittest_SOURCES = virtualsocket_unittest.cc +virtualsocket_unittest_LDADD = libcrickettest.la libcricketbase.la -lpthread + +EXTRA_DIST = diskcache_win32.h \ + diskcache_win32.cc \ + win32.h \ + winping.h \ + winping.cc \ + win32filesystem.cc \ + win32socketserver.cc \ + win32socketserver.h \ + win32window.h \ + winfirewall.h \ + winfirewall.cc \ + schanneladapter.h \ + schanneladapter.cc \ + sec_buffer.h diff --git a/third_party/libjingle/files/talk/base/asyncfile.h b/third_party/libjingle/files/talk/base/asyncfile.h new file mode 100644 index 0000000..1437979 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncfile.h @@ -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. + */ + +#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: + 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/third_party/libjingle/files/talk/base/asynchttprequest.cc b/third_party/libjingle/files/talk/base/asynchttprequest.cc new file mode 100644 index 0000000..bf7bc85 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asynchttprequest.cc @@ -0,0 +1,155 @@ +#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" +#include "talk/base/asynchttprequest.h" + +using namespace talk_base; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +HttpMonitor::HttpMonitor(SocketServer *ss) { + ASSERT(talk_base::Thread::Current() != NULL); + ss_ = ss; + reset(); +} + +void HttpMonitor::Connect(talk_base::HttpClient *http) { + http->SignalHttpClientComplete.connect(this, + &HttpMonitor::OnHttpClientComplete); +} + +void HttpMonitor::OnHttpClientComplete(talk_base::HttpClient * http, int err) { + complete_ = true; + err_ = err; + ss_->WakeUp(); +} + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +talk_base::Socket * SslSocketFactory::CreateSocket(int type) { + return factory_->CreateSocket(type); +} + +talk_base::AsyncSocket * SslSocketFactory::CreateAsyncSocket(int type) { + talk_base::AsyncSocket * socket = factory_->CreateAsyncSocket(type); + if (!socket) + return 0; + + // Binary logging happens at the lowest level + if (!logging_label_.empty() && binary_mode_) { + socket = new talk_base::LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), + binary_mode_); + } + + if (proxy_.type) { + talk_base::AsyncSocket * proxy_socket = 0; + if (proxy_.type == talk_base::PROXY_SOCKS5) { + proxy_socket = new talk_base::AsyncSocksProxySocket(socket, proxy_.address, + proxy_.username, proxy_.password); + } else { + // Note: we are trying unknown proxies as HTTPS currently + proxy_socket = new talk_base::AsyncHttpsProxySocket(socket, + agent_, proxy_.address, + proxy_.username, proxy_.password); + } + if (!proxy_socket) { + delete socket; + return 0; + } + socket = proxy_socket; // for our purposes the proxy is now the socket + } + + if (!hostname_.empty()) { + talk_base::SSLAdapter * ssl_adapter = talk_base::SSLAdapter::Create(socket); + ssl_adapter->set_ignore_bad_cert(ignore_bad_cert_); + ssl_adapter->StartSSL(hostname_.c_str(), true); + socket = ssl_adapter; + } + + // Regular logging occurs at the highest level + if (!logging_label_.empty() && !binary_mode_) { + socket = new talk_base::LoggingSocketAdapter(socket, logging_level_, + logging_label_.c_str(), + binary_mode_); + } + return socket; +} + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +/////////////////////////////////////////////////////////////////////////////// + +const int kDefaultHTTPTimeout = 30 * 1000; // 30 sec + +AsyncHttpRequest::AsyncHttpRequest(const std::string &user_agent) +: firewall_(0), port_(80), secure_(false), + timeout_(kDefaultHTTPTimeout), fail_redirect_(false), + client_(user_agent.c_str(), NULL) +{ +} + +void AsyncHttpRequest::DoWork() { + // TODO: Rewrite this to use the thread's native socket server, and a more + // natural flow? + + talk_base::PhysicalSocketServer physical; + talk_base::SocketServer * ss = &physical; + if (firewall_) { + ss = new talk_base::FirewallSocketServer(ss, firewall_); + } + + SslSocketFactory factory(ss, client_.agent()); + factory.SetProxy(proxy_); + if (secure_) + factory.UseSSL(host_.c_str()); + + //factory.SetLogging("AsyncHttpRequest"); + + talk_base::ReuseSocketPool pool(&factory); + client_.set_pool(&pool); + + bool transparent_proxy = (port_ == 80) + && ((proxy_.type == talk_base::PROXY_HTTPS) + || (proxy_.type == talk_base::PROXY_UNKNOWN)); + + if (transparent_proxy) { + client_.set_proxy(proxy_); + } + client_.set_fail_redirect(fail_redirect_); + + talk_base::SocketAddress server(host_, port_); + client_.set_server(server); + + HttpMonitor monitor(ss); + monitor.Connect(&client_); + client_.start(); + ss->Wait(timeout_, true); + if (!monitor.done()) { + LOG(LS_INFO) << "AsyncHttpRequest request timed out"; + client_.reset(); + return; + } + + if (monitor.error()) { + LOG(LS_INFO) << "AsyncHttpRequest request error: " << monitor.error(); + if (monitor.error() == talk_base::HE_AUTH) { + //proxy_auth_required_ = true; + } + return; + } + + std::string value; + if (client_.response().hasHeader(HH_LOCATION, &value)) { + response_redirect_ = value.c_str(); + } +} diff --git a/third_party/libjingle/files/talk/base/asynchttprequest.h b/third_party/libjingle/files/talk/base/asynchttprequest.h new file mode 100644 index 0000000..65bfa26 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asynchttprequest.h @@ -0,0 +1,140 @@ +#ifndef _ASYNCHTTPREQUEST_H_ +#define _ASYNCHTTPREQUEST_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/signalthread.h" + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// AsyncHttpRequest +// Performs an HTTP request on a background thread. Notifies on the foreground +// thread once the request is done (successfully or unsuccessfully). +/////////////////////////////////////////////////////////////////////////////// + +class FirewallManager; +class MemoryStream; + +class AsyncHttpRequest: + public SignalThread, + public sigslot::has_slots<> { +public: + AsyncHttpRequest(const std::string &user_agent); + + void set_proxy(const talk_base::ProxyInfo& proxy) { + proxy_ = proxy; + } + void set_firewall(talk_base::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(); } + +private: + // SignalThread Interface + virtual void DoWork(); + + talk_base::ProxyInfo proxy_; + talk_base::FirewallManager * firewall_; + std::string host_; + int port_; + bool secure_; + int timeout_; + bool fail_redirect_; + HttpClient client_; + std::string response_redirect_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// HttpMonitor +/////////////////////////////////////////////////////////////////////////////// + +class HttpMonitor : public sigslot::has_slots<> { +public: + HttpMonitor(SocketServer *ss); + + void reset() { complete_ = false; } + + bool done() const { return complete_; } + int error() const { return err_; } + + void Connect(talk_base::HttpClient* http); + void OnHttpClientComplete(talk_base::HttpClient * http, int err); + +private: + bool complete_; + int err_; + SocketServer *ss_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// SslSocketFactory +/////////////////////////////////////////////////////////////////////////////// + +class SslSocketFactory : public talk_base::SocketFactory { + public: + SslSocketFactory(talk_base::SocketFactory * factory, const std::string &user_agent) + : factory_(factory), logging_level_(talk_base::LS_VERBOSE), + binary_mode_(false), agent_(user_agent) { } + + void UseSSL(const char * hostname) { hostname_ = hostname; } + void DisableSSL() { hostname_.clear(); } + + void SetProxy(const talk_base::ProxyInfo& proxy) { proxy_ = proxy; } + const talk_base::ProxyInfo& proxy() const { return proxy_; } + bool ignore_bad_cert() {return ignore_bad_cert_;} + void SetIgnoreBadCert(bool ignore) { ignore_bad_cert_ = ignore; } + + void SetLogging(talk_base::LoggingSeverity level, const std::string& label, + bool binary_mode = false) { + logging_level_ = level; + logging_label_ = label; + binary_mode_ = binary_mode; + } + + virtual talk_base::Socket * CreateSocket(int type); + virtual talk_base::AsyncSocket * CreateAsyncSocket(int type); + +private: + talk_base::SocketFactory * factory_; + talk_base::ProxyInfo proxy_; + std::string hostname_, logging_label_; + talk_base::LoggingSeverity logging_level_; + bool binary_mode_; + std::string agent_; + bool ignore_bad_cert_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base_ + +#endif // _ASYNCHTTPREQUEST_H_ diff --git a/third_party/libjingle/files/talk/base/asyncpacketsocket.cc b/third_party/libjingle/files/talk/base/asyncpacketsocket.cc new file mode 100644 index 0000000..37ba058 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncpacketsocket.cc @@ -0,0 +1,84 @@ +/* + * 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/asyncpacketsocket.h" + +namespace talk_base { + +AsyncPacketSocket::AsyncPacketSocket(AsyncSocket* socket) : socket_(socket) { +} + +AsyncPacketSocket::~AsyncPacketSocket() { + delete socket_; +} + +SocketAddress AsyncPacketSocket::GetLocalAddress() const { + return socket_->GetLocalAddress(); +} + +SocketAddress AsyncPacketSocket::GetRemoteAddress() const { + return socket_->GetRemoteAddress(); +} + +int AsyncPacketSocket::Bind(const SocketAddress& addr) { + return socket_->Bind(addr); +} + +int AsyncPacketSocket::Connect(const SocketAddress& addr) { + return socket_->Connect(addr); +} + +int AsyncPacketSocket::Send(const void *pv, size_t cb) { + return socket_->Send(pv, cb); +} + +int AsyncPacketSocket::SendTo( + const void *pv, size_t cb, const SocketAddress& addr) { + return socket_->SendTo(pv, cb, addr); +} + +int AsyncPacketSocket::Close() { + return socket_->Close(); +} + +int AsyncPacketSocket::SetOption(Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int AsyncPacketSocket::GetError() const { + return socket_->GetError(); +} + +void AsyncPacketSocket::SetError(int error) { + return socket_->SetError(error); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/asyncpacketsocket.h b/third_party/libjingle/files/talk/base/asyncpacketsocket.h new file mode 100644 index 0000000..c6172a7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncpacketsocket.h @@ -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. + */ + +#ifndef TALK_BASE_ASYNCPACKETSOCKET_H__ +#define TALK_BASE_ASYNCPACKETSOCKET_H__ + +#include "talk/base/asyncsocket.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(AsyncSocket* socket); + virtual ~AsyncPacketSocket(); + + // Relevant socket methods: + 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 Close(); + virtual int SetOption(Socket::Option opt, int value); + virtual int GetError() const; + virtual void SetError(int error); + + // Emitted each time a packet is read. + sigslot::signal4<const char*, size_t, const SocketAddress&, AsyncPacketSocket*> SignalReadPacket; + +protected: + AsyncSocket* socket_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_ASYNCPACKETSOCKET_H__ diff --git a/third_party/libjingle/files/talk/base/asyncsocket.h b/third_party/libjingle/files/talk/base/asyncsocket.h new file mode 100644 index 0000000..ad0dfb4 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncsocket.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_ASYNCSOCKET_H__ +#define TALK_BASE_ASYNCSOCKET_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/socket.h" + +namespace talk_base { + +// Provides the ability to perform socket I/O asynchronously. +class AsyncSocket : public Socket, public sigslot::has_slots<> { +public: + virtual ~AsyncSocket() {} + + 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 + // TODO: error +}; + +class AsyncSocketAdapter : public AsyncSocket { +public: + AsyncSocketAdapter(Socket * socket) : socket_(socket) { + } + AsyncSocketAdapter(AsyncSocket * socket) : socket_(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); + } + virtual ~AsyncSocketAdapter() { delete 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 Socket *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 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); } + + Socket * socket_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_ASYNCSOCKET_H__ diff --git a/third_party/libjingle/files/talk/base/asynctcpsocket.cc b/third_party/libjingle/files/talk/base/asynctcpsocket.cc new file mode 100644 index 0000000..84d1401 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asynctcpsocket.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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include "talk/base/asynctcpsocket.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +namespace talk_base { + +const size_t MAX_PACKET_SIZE = 64 * 1024; + +typedef uint16 PacketLength; +const size_t PKT_LEN_SIZE = sizeof(PacketLength); + +const size_t BUF_SIZE = MAX_PACKET_SIZE + PKT_LEN_SIZE; + +AsyncTCPSocket::AsyncTCPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket), insize_(BUF_SIZE), inpos_(0), outsize_(BUF_SIZE), outpos_(0) { + inbuf_ = new char[insize_]; + outbuf_ = new char[outsize_]; + + ASSERT(socket_ != 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); +} + +AsyncTCPSocket::~AsyncTCPSocket() { + delete [] inbuf_; + delete [] outbuf_; +} + +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::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(data + PKT_LEN_SIZE, pkt_len, remote_addr, this); + + 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 == socket_); + + 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) << "recvfrom: " << errno << " " << std::strerror(errno); + } + return; + } + + inpos_ += len; + + ProcessInput(inbuf_, inpos_); + + if (inpos_ >= insize_) { + LOG(INFO) << "input buffer overflow"; + ASSERT(false); + inpos_ = 0; + } +} + +void AsyncTCPSocket::OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + + if (outpos_ > 0) { + Flush(); + } +} + +void AsyncTCPSocket::OnCloseEvent(AsyncSocket* socket, int error) { + SignalClose(this, error); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/asynctcpsocket.h b/third_party/libjingle/files/talk/base/asynctcpsocket.h new file mode 100644 index 0000000..2509451 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asynctcpsocket.h @@ -0,0 +1,68 @@ +/* + * 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_ASYNCTCPSOCKET_H__ +#define TALK_BASE_ASYNCTCPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.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: + AsyncTCPSocket(AsyncSocket* socket); + virtual ~AsyncTCPSocket(); + + virtual int Send(const void *pv, size_t cb); + virtual int SendTo(const void *pv, size_t cb, const SocketAddress& addr); + + sigslot::signal1<AsyncTCPSocket*> SignalConnect; + sigslot::signal2<AsyncTCPSocket*,int> SignalClose; + +protected: + int SendRaw(const void * pv, size_t cb); + virtual void ProcessInput(char * data, size_t& len); + +private: + char* inbuf_, * outbuf_; + size_t insize_, inpos_, outsize_, outpos_; + + 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); +}; + +} // namespace talk_base + +#endif // TALK_BASE_ASYNCTCPSOCKET_H__ diff --git a/third_party/libjingle/files/talk/base/asyncudpsocket.cc b/third_party/libjingle/files/talk/base/asyncudpsocket.cc new file mode 100644 index 0000000..a0c967d --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncudpsocket.cc @@ -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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include <cassert> +#include <cstring> +#include <iostream> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/logging.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +namespace talk_base { + +const int BUF_SIZE = 64 * 1024; + +AsyncUDPSocket::AsyncUDPSocket(AsyncSocket* socket) : AsyncPacketSocket(socket) { + size_ = BUF_SIZE; + buf_ = new char[size_]; + + assert(socket_); + // The socket should start out readable but not writable. + socket_->SignalReadEvent.connect(this, &AsyncUDPSocket::OnReadEvent); +} + +AsyncUDPSocket::~AsyncUDPSocket() { + delete [] buf_; +} + +void AsyncUDPSocket::OnReadEvent(AsyncSocket* socket) { + assert(socket == socket_); + + SocketAddress remote_addr; + int len = socket_->RecvFrom(buf_, size_, &remote_addr); + if (len < 0) { + // TODO: Do something better like forwarding the error to the user. + PLOG(LS_ERROR, socket_->GetError()) << "recvfrom"; + 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(buf_, (size_t)len, remote_addr, this); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/asyncudpsocket.h b/third_party/libjingle/files/talk/base/asyncudpsocket.h new file mode 100644 index 0000000..8232938 --- /dev/null +++ b/third_party/libjingle/files/talk/base/asyncudpsocket.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_ASYNCUDPSOCKET_H__ +#define TALK_BASE_ASYNCUDPSOCKET_H__ + +#include "talk/base/asyncpacketsocket.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: + AsyncUDPSocket(AsyncSocket* socket); + virtual ~AsyncUDPSocket(); + +private: + char* buf_; + size_t size_; + + // Called when the underlying socket is ready to be read from. + void OnReadEvent(AsyncSocket* socket); +}; + +// Creates a new socket for sending asynchronous UDP packets using an +// asynchronous socket from the given factory. +inline AsyncUDPSocket* CreateAsyncUDPSocket(SocketFactory* factory) { + return new AsyncUDPSocket(factory->CreateAsyncSocket(SOCK_DGRAM)); +} + +} // namespace talk_base + +#endif // TALK_BASE_ASYNCUDPSOCKET_H__ diff --git a/third_party/libjingle/files/talk/base/autodetectproxy.cc b/third_party/libjingle/files/talk/base/autodetectproxy.cc new file mode 100644 index 0000000..aa2feed --- /dev/null +++ b/third_party/libjingle/files/talk/base/autodetectproxy.cc @@ -0,0 +1,178 @@ +/* + * 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/autodetectproxy.h" +#include "talk/base/httpcommon.h" +#include "talk/xmpp/xmppclientsettings.h" +#include "talk/base/proxydetect.h" + +using namespace talk_base; + +enum { MSG_TIMEOUT = SignalThread::ST_MSG_FIRST_AVAILABLE }; + +const talk_base::ProxyType TEST_ORDER[] = { + talk_base::PROXY_HTTPS, /*talk_base::PROXY_SOCKS5,*/ talk_base::PROXY_UNKNOWN +}; + +AutoDetectProxy::AutoDetectProxy(const std::string& user_agent) +: agent_(user_agent), socket_(NULL), next_(0) +{ +} + +AutoDetectProxy::~AutoDetectProxy() { + delete socket_; +} + +void AutoDetectProxy::DoWork() { + 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"; + } + talk_base::Url<char> url(proxy_.address.IPAsString()); + if (url.valid()) { + LOG(LS_WARNING) << "AutoDetectProxy removing http prefix on proxy host"; + proxy_.address.SetIP(url.server()); + } + if (proxy_.type == talk_base::PROXY_UNKNOWN) { + //LOG(LS_INFO) << "Proxy classification start"; + Next(); + // Process I/O until Stop() + Thread::Current()->ProcessMessages(kForever); + } +} + +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_] >= talk_base::PROXY_UNKNOWN) { + Complete(talk_base::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(talk_base::ProxyType type) { + Thread::Current()->Clear(this, MSG_TIMEOUT); + socket_->Close(); + + proxy_.type = type; + talk_base::LoggingSeverity sev + = (proxy_.type == talk_base::PROXY_UNKNOWN) + ? talk_base::LS_ERROR : talk_base::LS_VERBOSE; + LOG_V(sev) << "AutoDetectProxy detected " << proxy_.address.ToString() + << " as type " << proxy_.type; + + Thread::Current()->MessageQueue::Stop(); +} + +void AutoDetectProxy::OnConnectEvent(talk_base::AsyncSocket * socket) { + std::string probe; + + switch (TEST_ORDER[next_]) { + case talk_base::PROXY_HTTPS: + probe.assign("\005\001" + "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"); + //probe = "CONNECT www.google.com:443 HTTP/1.0\r\n\r\n"; + break; + case talk_base::PROXY_SOCKS5: + probe.assign("\005\001\000", 3); + break; + } + + LOG(LS_VERBOSE) << "AutoDetectProxy probing type " << TEST_ORDER[next_] + << " sending " << probe.size() << " bytes"; + socket_->Send(probe.data(), probe.size()); +} + +void AutoDetectProxy::OnReadEvent(talk_base::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 talk_base::PROXY_HTTPS: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(talk_base::PROXY_SOCKS5); + return; + } + if ((len >= 5) && (strncmp(data, "HTTP/", 5) == 0)) { + Complete(talk_base::PROXY_HTTPS); + return; + } + break; + case talk_base::PROXY_SOCKS5: + if ((len >= 2) && (data[0] == '\x05')) { + Complete(talk_base::PROXY_SOCKS5); + return; + } + break; + } + + ++next_; + Next(); +} + +void AutoDetectProxy::OnCloseEvent(talk_base::AsyncSocket * socket, int error) { + LOG(LS_VERBOSE) << "AutoDetectProxy closed with error: " << error; + ++next_; + Next(); +} diff --git a/third_party/libjingle/files/talk/base/autodetectproxy.h b/third_party/libjingle/files/talk/base/autodetectproxy.h new file mode 100644 index 0000000..9633d31 --- /dev/null +++ b/third_party/libjingle/files/talk/base/autodetectproxy.h @@ -0,0 +1,68 @@ +#ifndef _AUTODETECTPROXY_H_ +#define _AUTODETECTPROXY_H_ + +#include "talk/base/sigslot.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/proxyinfo.h" +#include "talk/base/signalthread.h" +#include "talk/base/cryptstring.h" + +namespace buzz { +class XmppClientSettings; +} + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// AutoDetectProxy +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSocket; + +class AutoDetectProxy : public SignalThread, public sigslot::has_slots<> { +public: + AutoDetectProxy(const std::string& user_agent); + + const talk_base::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_, server_url_; + ProxyInfo proxy_; + AsyncSocket * socket_; + int next_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // _AUTODETECTPROXY_H_ diff --git a/third_party/libjingle/files/talk/base/base64.cc b/third_party/libjingle/files/talk/base/base64.cc new file mode 100644 index 0000000..53e5bac --- /dev/null +++ b/third_party/libjingle/files/talk/base/base64.cc @@ -0,0 +1,196 @@ + +//********************************************************************* +//* 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" + +using std::string; + +namespace talk_base { + +static const char fillchar = '='; +static const string::size_type np = string::npos; + +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 string::size_type Base64::DecodeTable[] = { +// 0 1 2 3 4 5 6 7 8 9 + np,np,np,np,np,np,np,np,np,np, // 0 - 9 + np,np,np,np,np,np,np,np,np,np, //10 -19 + np,np,np,np,np,np,np,np,np,np, //20 -29 + np,np,np,np,np,np,np,np,np,np, //30 -39 + np,np,np,62,np,np,np,63,52,53, //40 -49 + 54,55,56,57,58,59,60,61,np,np, //50 -59 + np,np,np,np,np, 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,np,np,np,np,np,np,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,np,np,np,np,np,np,np, //120 -129 + np,np,np,np,np,np,np,np,np,np, //130 -139 + np,np,np,np,np,np,np,np,np,np, //140 -149 + np,np,np,np,np,np,np,np,np,np, //150 -159 + np,np,np,np,np,np,np,np,np,np, //160 -169 + np,np,np,np,np,np,np,np,np,np, //170 -179 + np,np,np,np,np,np,np,np,np,np, //180 -189 + np,np,np,np,np,np,np,np,np,np, //190 -199 + np,np,np,np,np,np,np,np,np,np, //200 -209 + np,np,np,np,np,np,np,np,np,np, //210 -219 + np,np,np,np,np,np,np,np,np,np, //220 -229 + np,np,np,np,np,np,np,np,np,np, //230 -239 + np,np,np,np,np,np,np,np,np,np, //240 -249 + np,np,np,np,np,np //250 -256 +}; + +string Base64::encodeFromArray(const char * data, size_t len) { + size_t i; + char c; + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) { + c |= (data[i] >> 4) & 0x0f; + } + + ret.append(1, Base64Table[c]); + if (i < len) { + c = (data[i] << 2) & 0x3f; + if (++i < len) { + c |= (data[i] >> 6) & 0x03; + } + ret.append(1, Base64Table[c]); + } else { + ++i; + ret.append(1, fillchar); + } + + if (i < len) { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } else { + ret.append(1, fillchar); + } + } + + return(ret); +} + +string Base64::encode(const string& data) { + string::size_type i; + char c; + string::size_type len = data.length(); + string ret; + + ret.reserve(len * 2); + + for (i = 0; i < len; ++i) { + c = (data[i] >> 2) & 0x3f; + ret.append(1, Base64Table[c]); + c = (data[i] << 4) & 0x3f; + if (++i < len) { + c |= (data[i] >> 4) & 0x0f; + } + + ret.append(1, Base64Table[c]); + if (i < len) { + c = (data[i] << 2) & 0x3f; + if (++i < len) { + c |= (data[i] >> 6) & 0x03; + } + + ret.append(1, Base64Table[c]); + } else { + ++i; + ret.append(1, fillchar); + } + + if (i < len) { + c = data[i] & 0x3f; + ret.append(1, Base64Table[c]); + } else { + ret.append(1, fillchar); + } + } + + return(ret); +} + +string Base64::decode(const string& data) { + string::size_type i; + char c; + char c1; + string::size_type len = data.length(); + string ret; + + ret.reserve(len); + + for (i = 0; i < len; ++i) { + do { + c = static_cast<char>(DecodeTable[static_cast<unsigned char>(data[i])]); + } while (c == np && ++i < len); + + ++i; + + do { + c1 = static_cast<char>(DecodeTable[static_cast<unsigned char>(data[i])]); + } while (c == np && ++i < len); + + c = (c << 2) | ((c1 >> 4) & 0x3); + ret.append(1, c); + if (++i < len) { + c = data[i]; + if (fillchar == c) { + break; + } + + do { + c = static_cast<char>(DecodeTable[static_cast<unsigned char>(data[i])]); + } while (c == np && ++i < len); + c1 = ((c1 << 4) & 0xf0) | ((c >> 2) & 0xf); + ret.append(1, c1); + } + + if (++i < len) { + c1 = data[i]; + if (fillchar == c1) { + break; + } + + do { + c1 = static_cast<char>(DecodeTable[static_cast<unsigned char>(data[i])]); + } while (c == np && ++i < len); + + c = ((c << 6) & 0xc0) | c1; + ret.append(1, c); + } + } + + return(ret); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/base64.h b/third_party/libjingle/files/talk/base/base64.h new file mode 100644 index 0000000..2b58761 --- /dev/null +++ b/third_party/libjingle/files/talk/base/base64.h @@ -0,0 +1,32 @@ + +//********************************************************************* +//* 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> + +namespace talk_base { + +class Base64 +{ +public: + static std::string encode(const std::string & data); + static std::string decode(const std::string & data); + static std::string encodeFromArray(const char * data, size_t len); +private: + static const std::string Base64::Base64Table; + static const std::string::size_type Base64::DecodeTable[]; +}; + +} // namespace talk_base + +#endif // TALK_BASE_BASE64_H__ diff --git a/third_party/libjingle/files/talk/base/basicdefs.h b/third_party/libjingle/files/talk/base/basicdefs.h new file mode 100644 index 0000000..886c287 --- /dev/null +++ b/third_party/libjingle/files/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 TAKL_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/third_party/libjingle/files/talk/base/basictypes.h b/third_party/libjingle/files/talk/base/basictypes.h new file mode 100644 index 0000000..47588ad --- /dev/null +++ b/third_party/libjingle/files/talk/base/basictypes.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_BASE_BASICTYPES_H__ +#define TALK_BASE_BASICTYPES_H__ + +#ifdef COMPILER_MSVC +typedef __int64 int64; +#else +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef long int32; +typedef short int16; +typedef char int8; + +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#else +typedef unsigned long long uint64; +typedef long long int64; +#endif /* COMPILER_MSVC */ +typedef unsigned long uint32; +typedef unsigned short uint16; +typedef unsigned char uint8; + +#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; } +} + +// A macro to disallow the evil copy constructor and operator= functions +// This should be used in the private: declarations for a class +#define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ + TypeName(const TypeName&); \ + void operator=(const 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) + +#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 + +#endif // TALK_BASE_BASICTYPES_H__ diff --git a/third_party/libjingle/files/talk/base/bytebuffer.cc b/third_party/libjingle/files/talk/base/bytebuffer.cc new file mode 100644 index 0000000..7082e1a --- /dev/null +++ b/third_party/libjingle/files/talk/base/bytebuffer.cc @@ -0,0 +1,166 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <algorithm> +#include <cassert> + +#include "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include "talk/base/byteorder.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +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) { + return ReadBytes(reinterpret_cast<char*>(&val), 1); +} + +bool ByteBuffer::ReadUInt16(uint16& val) { + uint16 v; + if (!ReadBytes(reinterpret_cast<char*>(&v), 2)) { + return false; + } else { + val = NetworkToHost16(v); + return true; + } +} + +bool ByteBuffer::ReadUInt32(uint32& val) { + uint32 v; + if (!ReadBytes(reinterpret_cast<char*>(&v), 4)) { + return false; + } else { + val = NetworkToHost32(v); + return true; + } +} + +bool ByteBuffer::ReadString(std::string& val, size_t len) { + 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::WriteUInt32(uint32 val) { + uint32 v = HostToNetwork32(val); + WriteBytes(reinterpret_cast<const char*>(&v), 4); +} + +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::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/third_party/libjingle/files/talk/base/bytebuffer.h b/third_party/libjingle/files/talk/base/bytebuffer.h new file mode 100644 index 0000000..0a93194 --- /dev/null +++ b/third_party/libjingle/files/talk/base/bytebuffer.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_BYTEBUFFER_H__ +#define TALK_BASE_BYTEBUFFER_H__ + +#include <string> +#include "talk/base/basictypes.h" + +namespace talk_base { + +class ByteBuffer { +public: + ByteBuffer(); + ByteBuffer(const char* bytes, size_t len); + ByteBuffer(const char* bytes); // uses strlen + ~ByteBuffer(); + + const char* Data() const { return bytes_ + start_; } + size_t Length() { return end_ - start_; } + size_t Capacity() { return size_ - start_; } + + bool ReadUInt8(uint8& val); + bool ReadUInt16(uint16& val); + bool ReadUInt32(uint32& 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 WriteUInt32(uint32 val); + void WriteString(const std::string& val); + void WriteBytes(const char* val, size_t len); + + void Resize(size_t size); + void Shift(size_t size); + +private: + char* bytes_; + size_t size_; + size_t start_; + size_t end_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_BYTEBUFFER_H__ diff --git a/third_party/libjingle/files/talk/base/byteorder.h b/third_party/libjingle/files/talk/base/byteorder.h new file mode 100644 index 0000000..16834c2 --- /dev/null +++ b/third_party/libjingle/files/talk/base/byteorder.h @@ -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. + */ + +#ifndef TALK_BASE_BYTEORDER_H__ +#define TALK_BASE_BYTEORDER_H__ + +#include "talk/base/basictypes.h" + +#ifdef POSIX +extern "C" { +#include <arpa/inet.h> +} +#endif + +#ifdef WIN32 +#include <winsock2.h> +#endif + +namespace talk_base { + +inline uint16 HostToNetwork16(uint16 n) { + return htons(n); +} + +inline uint32 HostToNetwork32(uint32 n) { + return htonl(n); +} + +inline uint16 NetworkToHost16(uint16 n) { + return ntohs(n); +} + +inline uint32 NetworkToHost32(uint32 n) { + return ntohl(n); +} + +} // namespace talk_base + +#endif // TALK_BASE_BYTEORDER_H__ diff --git a/third_party/libjingle/files/talk/base/common.cc b/third_party/libjingle/files/talk/base/common.cc new file mode 100644 index 0000000..aa5c56a --- /dev/null +++ b/third_party/libjingle/files/talk/base/common.cc @@ -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. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <memory.h> + +#ifdef WIN32 +#include <windows.h> +#endif // WIN32 + +#include <algorithm> + +#include "talk/base/common.h" + +////////////////////////////////////////////////////////////////////// +// Assertions +////////////////////////////////////////////////////////////////////// + +namespace talk_base { + +void Break() { +#ifdef WIN32 + ::DebugBreak(); +#else // !WIN32 +#if _DEBUG_HAVE_BACKTRACE + OutputTrace(); +#endif + abort(); +#endif // !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 + fprintf(stderr, "%s(%d): %s @ %s\n", file, line, expression, function); +} + +} // namespace talk_base
\ No newline at end of file diff --git a/third_party/libjingle/files/talk/base/common.h b/third_party/libjingle/files/talk/base/common.h new file mode 100644 index 0000000..f85bf89 --- /dev/null +++ b/third_party/libjingle/files/talk/base/common.h @@ -0,0 +1,114 @@ +/* + * 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__ + +#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) +#define stdmax(x,y) std::max(x,y) +#else +#define stdmax(x,y) _max(x,y) +#endif + + +#define ARRAY_SIZE(x) (static_cast<int>((sizeof(x)/sizeof(x[0])))) + +///////////////////////////////////////////////////////////////////////////// +// Assertions +///////////////////////////////////////////////////////////////////////////// + +#ifdef 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 void Assert(bool result, const char * function, const char * file, int line, const char * expression) { + if (!result) { + LogAssert(function, file, line, expression); + Break(); + } +} + +}; // namespace talk_base + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#define __FUNCTION__ "" +#endif + +#ifndef ASSERT +#define ASSERT(x) 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 + +#ifndef ASSERT +#define ASSERT(x) (void)0 +#endif + +#ifndef VERIFY +#define VERIFY(x) (void)(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 + +////////////////////////////////////////////////////////////////////// + +#endif // TALK_BASE_COMMON_H__ diff --git a/third_party/libjingle/files/talk/base/convert.h b/third_party/libjingle/files/talk/base/convert.h new file mode 100644 index 0000000..26c6d32 --- /dev/null +++ b/third_party/libjingle/files/talk/base/convert.h @@ -0,0 +1,149 @@ +/* + * 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 _CONVERT_H_ +#define _CONVERT_H_ + +#include <string> +#include <windows.h> + +#ifndef NO_ATL +#include <atlstr.h> +#endif // NO_ATL + +#include "talk/base/basictypes.h" + +class Utf8 { +public: +#ifndef NO_ATL + explicit Utf8(const CString & str) { + *this = str; + } +#else + explicit Utf8(const wchar_t *str) { + *this = str; + } +#endif + + explicit Utf8() {} +#ifndef NO_ATL + inline Utf8& operator =(const CString & str) { + // TODO: deal with errors + int len8 = WideCharToMultiByte(CP_UTF8, 0, str.GetString(), str.GetLength(), + NULL, 0, NULL, NULL); + char * ns = static_cast<char*>(_alloca(len8)); + WideCharToMultiByte(CP_UTF8, 0, str.GetString(), str.GetLength(), + ns, len8, NULL, NULL); + str_.assign(ns, len8); + return *this; + } +#else +inline Utf8& operator =(const wchar_t *str) { + // TODO: deal with errors + int len8 = WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), + NULL, 0, NULL, NULL); + char * ns = static_cast<char*>(_alloca(len8)); + WideCharToMultiByte(CP_UTF8, 0, str, wcslen(str), + ns, len8, NULL, NULL); + str_.assign(ns, len8); + return *this; + } +#endif // NO_ATL + + inline operator const std::string & () const { + return str_; + } + + inline const char * AsSz() const { + return str_.c_str(); + } + + // Deprecated + inline const std::string & AsString() const { + return str_; + } + + // Deprecated + inline int Len8() const { + return (int)str_.length(); + } + +private: + DISALLOW_EVIL_CONSTRUCTORS(Utf8); + std::string str_; +}; + +class Utf16 { +public: + explicit Utf16(const std::string & str) { + // TODO: deal with errors + int len16 = MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, + NULL, 0); +#ifndef NO_ATL + wchar_t * ws = cstr_.GetBuffer(len16); + MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), ws, len16); + cstr_.ReleaseBuffer(len16); +#else + str_ = new wchar_t[len16]; + MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, str_, len16); +#endif + } + +#ifndef NO_ATL + inline operator const CString & () const { + return cstr_; + } + // Deprecated + inline const CString & AsCString() const { + return cstr_; + } + // Deprecated + inline int Len16() const { + return cstr_.GetLength(); + } + inline const wchar_t * AsWz() const { + return cstr_.GetString(); + } +#else + ~Utf16() { + delete[] str_; + } + inline const wchar_t * AsWz() const { + return str_; + } +#endif + +private: + DISALLOW_EVIL_CONSTRUCTORS(Utf16); +#ifndef NO_ATL + CString cstr_; +#else + wchar_t *str_; +#endif +}; + +#endif // _CONVERT_H_ diff --git a/third_party/libjingle/files/talk/base/criticalsection.h b/third_party/libjingle/files/talk/base/criticalsection.h new file mode 100644 index 0000000..fbe7382 --- /dev/null +++ b/third_party/libjingle/files/talk/base/criticalsection.h @@ -0,0 +1,120 @@ +/* + * 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_settype(&mutex_attribute, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&mutex_, &mutex_attribute); + } + ~CriticalSection() { + pthread_mutex_destroy(&mutex_); + } + void Enter() { + pthread_mutex_lock(&mutex_); + } + void Leave() { + pthread_mutex_unlock(&mutex_); + } +private: + pthread_mutex_t mutex_; +}; +#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/third_party/libjingle/files/talk/base/cryptstring.h b/third_party/libjingle/files/talk/base/cryptstring.h new file mode 100644 index 0000000..64a7e57 --- /dev/null +++ b/third_party/libjingle/files/talk/base/cryptstring.h @@ -0,0 +1,185 @@ +/* + * 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 <string> +#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; +}; + +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(); } +}; + +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(); } + +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; + } + private: + std::string password_; +}; + +} + +#endif // _TALK_BASE_CRYPTSTRING_H_ diff --git a/third_party/libjingle/files/talk/base/diskcache.cc b/third_party/libjingle/files/talk/base/diskcache.cc new file mode 100644 index 0000000..44e37d9 --- /dev/null +++ b/third_party/libjingle/files/talk/base/diskcache.cc @@ -0,0 +1,362 @@ +/* + * 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/basicdefs.h" +#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); + if (1 != sscanf(pathname.extension().c_str(), ".%u", index)) + return false; + + 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/third_party/libjingle/files/talk/base/diskcache.h b/third_party/libjingle/files/talk/base/diskcache.h new file mode 100644 index 0000000..b42e3e0 --- /dev/null +++ b/third_party/libjingle/files/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(); + ~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/third_party/libjingle/files/talk/base/diskcache_win32.cc b/third_party/libjingle/files/talk/base/diskcache_win32.cc new file mode 100644 index 0000000..ddc99ef --- /dev/null +++ b/third_party/libjingle/files/talk/base/diskcache_win32.cc @@ -0,0 +1,76 @@ +#include "talk/base/win32.h" +#include <shellapi.h> +#include <shlobj.h> +#include <tchar.h> + +#include <time.h> + +#include "talk/base/common.h" +#include "talk/base/diskcache.h" +#include "talk/base/pathutils.h" +#include "talk/base/stream.h" +#include "talk/base/stringencode.h" +#include "talk/base/stringutils.h" + +#include "talk/base/diskcache_win32.h" + +namespace talk_base { + +bool DiskCacheWin32::InitializeEntries() { + // Note: We could store the cache information in a separate file, for faster + // initialization. Figuring it out empirically works, too. + + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + + WIN32_FIND_DATA find_data; + HANDLE find_handle = FindFirstFile(path16.c_str(), &find_data); + if (find_handle != INVALID_HANDLE_VALUE) { + do { + size_t index; + std::string id; + if (!FilenameToId(ToUtf8(find_data.cFileName), &id, &index)) + continue; + + Entry* entry = GetOrCreateEntry(id, true); + entry->size += find_data.nFileSizeLow; + total_size_ += find_data.nFileSizeLow; + entry->streams = _max(entry->streams, index + 1); + FileTimeToUnixTime(find_data.ftLastWriteTime, &entry->last_modified); + + } while (FindNextFile(find_handle, &find_data)); + + FindClose(find_handle); + } + + return true; +} + +bool DiskCacheWin32::PurgeFiles() { + std::wstring path16 = ToUtf16(folder_); + path16.append(1, '*'); + path16.append(1, '\0'); + + SHFILEOPSTRUCT file_op = { 0 }; + file_op.wFunc = FO_DELETE; + file_op.pFrom = path16.c_str(); + file_op.fFlags = FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT + | FOF_NORECURSION | FOF_FILESONLY; + if (0 != SHFileOperation(&file_op)) { + LOG_F(LS_ERROR) << "Couldn't delete cache files"; + return false; + } + + return true; +} + +bool DiskCacheWin32::FileExists(const std::string& filename) const { + DWORD result = ::GetFileAttributes(ToUtf16(filename).c_str()); + return (INVALID_FILE_ATTRIBUTES != result); +} + +bool DiskCacheWin32::DeleteFile(const std::string& filename) const { + return ::DeleteFile(ToUtf16(filename).c_str()) != 0; +} + +} diff --git a/third_party/libjingle/files/talk/base/diskcache_win32.h b/third_party/libjingle/files/talk/base/diskcache_win32.h new file mode 100644 index 0000000..aa5a1b6 --- /dev/null +++ b/third_party/libjingle/files/talk/base/diskcache_win32.h @@ -0,0 +1,28 @@ +// +// DiskCacheWin32.h +// Macshroom +// +// Created by Moishe Lettvin on 11/7/06. +// Copyright (C) 2006 Google Inc. All rights reserved. +// +// + +#ifndef TALK_BASE_DISKCACHEWIN32_H__ +#define TALK_BASE_DISKCACHEWIN32_H__ + +#include "talk/base/diskcache.h" + +namespace talk_base { + +class DiskCacheWin32 : public DiskCache { + protected: + virtual bool InitializeEntries(); + virtual bool PurgeFiles(); + + virtual bool FileExists(const std::string& filename) const; + virtual bool DeleteFile(const std::string& filename) const; +}; + +} + +#endif // TALK_BASE_DISKCACHEWIN32_H__ diff --git a/third_party/libjingle/files/talk/base/diskcachestd.cc b/third_party/libjingle/files/talk/base/diskcachestd.cc new file mode 100644 index 0000000..f50c7d7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/diskcachestd.cc @@ -0,0 +1,30 @@ +// +// DiskCacheStd.cc +// Macshroom +// +// Created by Moishe Lettvin on 11/7/06. +// Copyright (C) 2006 Google Inc. All rights reserved. +// +// + +#include "diskcachestd.h" + +namespace talk_base { + +bool DiskCacheStd::InitializeEntries() { + return false; +} + +bool DiskCacheStd::PurgeFiles() { + return false; +} + +bool DiskCacheStd::FileExists(const std::string& filename) const { + return false; +} + +bool DiskCacheStd::DeleteFile(const std::string& filename) const { + return false; +} + +} diff --git a/third_party/libjingle/files/talk/base/diskcachestd.h b/third_party/libjingle/files/talk/base/diskcachestd.h new file mode 100644 index 0000000..b676771 --- /dev/null +++ b/third_party/libjingle/files/talk/base/diskcachestd.h @@ -0,0 +1,28 @@ +// +// DiskCacheStd.h +// Macshroom +// +// Created by Moishe Lettvin on 11/7/06. +// Copyright (C) 2006 Google Inc. All rights reserved. +// +// + +#ifndef TALK_BASE_DISKCACHESTD_H__ +#define TALK_BASE_DISKCACHESTD_H__ + +#include "talk/base/diskcache.h" + +namespace talk_base { + +class DiskCacheStd : public DiskCache { + protected: + virtual bool InitializeEntries(); + virtual bool PurgeFiles(); + + virtual bool FileExists(const std::string& filename) const; + virtual bool DeleteFile(const std::string& filename) const; +}; + +} + +#endif // TALK_BASE_DISKCACHESTD_H__ diff --git a/third_party/libjingle/files/talk/base/event.h b/third_party/libjingle/files/talk/base/event.h new file mode 100644 index 0000000..ca15c38 --- /dev/null +++ b/third_party/libjingle/files/talk/base/event.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_EVENT_H__ +#define TALK_BASE_EVENT_H__ + +namespace talk_base { + +#ifdef WIN32 +class Event { +public: + Event() { + event_ = CreateEvent(NULL, FALSE, FALSE, NULL); + } + ~Event() { + CloseHandle(event_); + } + void Set() { + SetEvent(event_); + } + void Reset() { + ResetEvent(event_); + } + void Wait() { + WaitForSingleObject(event_, INFINITE); + } +private: + HANDLE event_; +}; +#endif + +#ifdef POSIX +#include <cassert> +class Event { + Event() { + assert(false); + } + ~Event() { + assert(false); + } + void Set() { + assert(false); + } + void Reset() { + assert(false); + } + void Wait() { + assert(false); + } +}; +#endif + +} // namespace talk_base + +#endif // TALK_BASE_EVENT_H__ diff --git a/third_party/libjingle/files/talk/base/fileutils.cc b/third_party/libjingle/files/talk/base/fileutils.cc new file mode 100644 index 0000000..12c2576 --- /dev/null +++ b/third_party/libjingle/files/talk/base/fileutils.cc @@ -0,0 +1,273 @@ +/* + * 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 <errno.h> +#include <cassert> + +#ifdef WIN32 +#include "talk/base/convert.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 256 +#endif + +namespace talk_base { + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A Directoryraverser 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(Utf16(d).AsWz(), &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 Utf8(data_.cFileName).AsString(); +#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 +return 0; +#else + return stat_.st_mtime; +#endif +} + +Filesystem *Filesystem::default_filesystem_ = 0; + + +bool Filesystem::CreateFolder(const Pathname &pathname) +{ + return EnsureDefaultFilesystem()->CreateFolderI(pathname); +} + +FileStream *Filesystem::OpenFile(const Pathname &filename, + const std::string &mode) +{ + return EnsureDefaultFilesystem()->OpenFileI(filename, mode); +} + +bool Filesystem::DeleteFile(const Pathname &filename) +{ + return EnsureDefaultFilesystem()->DeleteFileI(filename); +} + +bool Filesystem::MoveFile(const Pathname &old_path, const Pathname &new_path) +{ + return EnsureDefaultFilesystem()->MoveFileI(old_path, new_path); +} + +bool Filesystem::CopyFile(const Pathname &old_path, const Pathname &new_path) +{ + return EnsureDefaultFilesystem()->CopyFileI(old_path, new_path); +} + +bool Filesystem::IsFolder(const Pathname& pathname) +{ + return EnsureDefaultFilesystem()->IsFolderI(pathname); +} + +bool Filesystem::FileExists(const Pathname& pathname) +{ + return EnsureDefaultFilesystem()->FileExistsI(pathname); +} + +bool Filesystem::IsTemporaryPath(const Pathname& pathname) +{ + return EnsureDefaultFilesystem()->IsTemporaryPathI(pathname); +} + +bool Filesystem::GetTemporaryFolder(Pathname &path, bool create, + const std::string *append) +{ + return EnsureDefaultFilesystem()->GetTemporaryFolderI(path,create, append); +} + +std::string Filesystem::TempFilename(const Pathname &dir, const std::string &prefix) +{ + return EnsureDefaultFilesystem()->TempFilenameI(dir, prefix); +} + +bool Filesystem::GetFileSize(const Pathname &dir, size_t *size) +{ + return EnsureDefaultFilesystem()->GetFileSizeI(dir, size); +} + +Filesystem *Filesystem::EnsureDefaultFilesystem() +{ + if (!default_filesystem_) +#ifdef WIN32 + default_filesystem_ = new Win32Filesystem(); +#else + default_filesystem_ = new UnixFilesystem(); +#endif + return default_filesystem_; +} + +bool CreateUniqueFile(Pathname& path, bool create_empty) { + LOG(LS_INFO) << "Path " << path.pathname() << std::endl; + // If not 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 not filename is supplied, use a temporary name + if (path.filename().empty()) { + std::string folder(path.folder()); + std::string filename = Filesystem::TempFilename(folder, "gt"); + path.SetFilename(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::FileExists(pathname)) { + if (create_empty) { + FileStream* fs = Filesystem::OpenFile(pathname,"w"); + delete fs; + } + return true; + } + version += 1; + char version_base[MAX_PATH]; + talk_base::sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u", + basename.c_str(), version); + path.SetBasename(version_base); + } + return true; +} +} diff --git a/third_party/libjingle/files/talk/base/fileutils.h b/third_party/libjingle/files/talk/base/fileutils.h new file mode 100644 index 0000000..868f4c2 --- /dev/null +++ b/third_party/libjingle/files/talk/base/fileutils.h @@ -0,0 +1,185 @@ +/* + * 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 _WINDOWS +#include <windows.h> +#else +#include <sys/types.h> +#include <dirent.h> +#include <sys/stat.h> +#include <unistd.h> +#endif + +#include "talk/base/common.h" + +namespace talk_base { + +class FileStream; +class Pathname; + +////////////////////////// +// Directory Iterator // +////////////////////////// + +// A DirectoryTraverser 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 { + + public: + // Constructor + DirectoryIterator(); + + // Destructor + ~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 + bool Iterate(const Pathname &path); + + // Advances to the next file + // returns true if there were more files in the directory. + bool Next(); + + // returns true if the file currently pointed to is a directory + bool IsDirectory() const; + + // returns the name of the file currently pointed to + std::string Name() const; + + // returns the size of the file currently pointed to + size_t FileSize() const; + + // returns the last modified time of the file currently poitned to + time_t FileModifyTime() const; + + private: + std::string directory_; +#ifdef _WINDOWS + WIN32_FIND_DATA data_; + HANDLE handle_; +#else + DIR *dir_; + struct dirent *dirent_; + struct stat stat_; +#endif +}; + +class Filesystem { + public: + + virtual bool CreateFolderI(const Pathname &pathname) = 0; + + // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise, + // returns NULL. + virtual FileStream *OpenFileI(const Pathname &filename, + const std::string &mode) = 0; + + // This will attempt to delete the path located at filename. If filename is a file, + // it will be unlinked. If the path is a directory, it will recursively unlink and remove + // all the files and directory within it + virtual bool DeleteFileI(const Pathname &filename) = 0; + + // Creates a directory. This will call itself recursively to create /foo/bar even if + // /foo does not exist. + // Returns TRUE if function succeeds + + // 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 MoveFileI(const Pathname &old_path, const Pathname &new_path) = 0; + + // 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 CopyFileI(const Pathname &old_path, const Pathname &new_path) = 0; + + // Returns true if a pathname is a directory + virtual bool IsFolderI(const Pathname& pathname) = 0; + + // Returns true if a file exists at this path + virtual bool FileExistsI(const Pathname& pathname) = 0; + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPathI(const Pathname& pathname) = 0; + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolderI(Pathname &path, bool create, + const std::string *append) = 0; + + virtual std::string TempFilenameI(const Pathname &dir, const std::string &prefix) = 0; + + virtual bool GetFileSizeI(const Pathname &dir, size_t *size) = 0; + + static Filesystem *default_filesystem(void) { ASSERT(default_filesystem_!=NULL); return default_filesystem_; } + static void set_default_filesystem(Filesystem *filesystem) {default_filesystem_ = filesystem; } + + + static bool CreateFolder(const Pathname &pathname); + + static FileStream *OpenFile(const Pathname &filename, + const std::string &mode); + static bool DeleteFile(const Pathname &filename); + static bool MoveFile(const Pathname &old_path, const Pathname &new_path); + static bool CopyFile(const Pathname &old_path, const Pathname &new_path); + static bool IsFolder(const Pathname& pathname); + static bool FileExists(const Pathname &pathname); + static bool IsTemporaryPath(const Pathname& pathname); + static bool GetTemporaryFolder(Pathname &path, bool create, + const std::string *append); + static std::string TempFilename(const Pathname &dir, const std::string &prefix); + static bool GetFileSize(const Pathname &dir, size_t *size); + + private: + static Filesystem *default_filesystem_; + static Filesystem *EnsureDefaultFilesystem(); + +}; + +// Generates a unique temporary filename in 'directory' with the given 'prefix' + std::string TempFilename(const Pathname &dir, const std::string &prefix); + + // 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(talk_base::Pathname& path, bool create_empty); + +} + +#endif // TALK_BASE_FILEUTILS_H__ diff --git a/third_party/libjingle/files/talk/base/firewallsocketserver.cc b/third_party/libjingle/files/talk/base/firewallsocketserver.cc new file mode 100644 index 0000000..49db8a8 --- /dev/null +++ b/third_party/libjingle/files/talk/base/firewallsocketserver.cc @@ -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. + */ + +#include <cassert> +#include <algorithm> +#ifdef OSX +#include <errno.h> +#endif + +#include "talk/base/firewallsocketserver.h" +#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) { + } + FirewallSocket(FirewallSocketServer * server, Socket * socket, int type) + : AsyncSocketAdapter(socket), server_(server), type_(type) { + } + + virtual int Connect(const SocketAddress& addr) { + if (type_ == SOCK_STREAM) { + if (!server_->Check(FP_TCP, FD_OUT, addr)) { + //LOG(INFO) << "FirewallSocket::Connect - Outbound TCP connection denied"; + // Note: handle this asynchronously? + SetError(EHOSTUNREACH); + return SOCKET_ERROR; + } + } + return AsyncSocketAdapter::Connect(addr); + } + virtual int Send(const void * pv, size_t cb) { + if (type_ == SOCK_DGRAM) { + if (!server_->Check(FP_UDP, FD_OUT, GetRemoteAddress())) { + //LOG(INFO) << "FirewallSocket::Send - Outbound UDP packet dropped"; + return static_cast<int>(cb); + } + } + return AsyncSocketAdapter::Send(pv, cb); + } + virtual int SendTo(const void * pv, size_t cb, const SocketAddress& addr) { + if (type_ == SOCK_DGRAM) { + if (!server_->Check(FP_UDP, FD_OUT, addr)) { + //LOG(INFO) << "FirewallSocket::SendTo - Outbound UDP packet dropped"; + return static_cast<int>(cb); + } + } + return AsyncSocketAdapter::SendTo(pv, cb, addr); + } + virtual int Recv(void * pv, size_t cb) { + if (type_ == SOCK_DGRAM) { + if (!server_->Check(FP_UDP, FD_IN, GetRemoteAddress())) { + while (true) { + int res = AsyncSocketAdapter::Recv(pv, cb); + if (res <= 0) + return res; + //LOG(INFO) << "FirewallSocket::Recv - Inbound UDP packet dropped"; + } + } + } + return AsyncSocketAdapter::Recv(pv, cb); + } + 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, FD_IN, *paddr)) + return res; + //LOG(INFO) << "FirewallSocket::RecvFrom - Inbound UDP packet dropped"; + } + } + return AsyncSocketAdapter::RecvFrom(pv, cb, paddr); + } + virtual Socket * Accept(SocketAddress *paddr) { + while (Socket * sock = AsyncSocketAdapter::Accept(paddr)) { + if (server_->Check(FP_TCP, FD_IN, *paddr)) + return sock; + sock->Close(); + delete sock; + //LOG(INFO) << "FirewallSocket::Accept - Inbound TCP connection denied"; + } + return 0; + } + +private: + FirewallSocketServer * server_; + int type_; +}; + +FirewallSocketServer::FirewallSocketServer(SocketServer * server, FirewallManager * manager) : server_(server), manager_(manager) { + if (manager_) + manager_->AddServer(this); +} + +FirewallSocketServer::~FirewallSocketServer() { + if (manager_) + manager_->RemoveServer(this); +} + +void FirewallSocketServer::AddRule(bool allow, FirewallProtocol p, FirewallDirection d, const SocketAddress& addr) { + Rule r; + r.allow = allow; + r.p = p; + r.d = d; + r.addr = addr; + CritScope scope(&crit_); + rules_.push_back(r); +} + +void FirewallSocketServer::ClearRules() { + CritScope scope(&crit_); + rules_.clear(); +} + +bool FirewallSocketServer::Check(FirewallProtocol p, FirewallDirection d, const SocketAddress& addr) { + 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.d != d) && (r.d != FD_ANY)) + continue; + if ((r.addr.ip() != addr.ip()) && !r.addr.IsAny()) + continue; + if ((r.addr.port() != addr.port()) && (r.addr.port() != 0)) + continue; + return r.allow; + } + return true; +} + +Socket* FirewallSocketServer::CreateSocket(int type) { + return WrapSocket(server_->CreateSocket(type), type); +} + +AsyncSocket* FirewallSocketServer::CreateAsyncSocket(int type) { + return WrapSocket(server_->CreateAsyncSocket(type), type); +} + +Socket * FirewallSocketServer::WrapSocket(Socket * sock, int type) { + if (!sock) + return NULL; + return new FirewallSocket(this, sock, type); +} + +AsyncSocket * FirewallSocketServer::WrapSocket(AsyncSocket * sock, int type) { + if (!sock) + 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/third_party/libjingle/files/talk/base/firewallsocketserver.h b/third_party/libjingle/files/talk/base/firewallsocketserver.h new file mode 100644 index 0000000..10c07e6 --- /dev/null +++ b/third_party/libjingle/files/talk/base/firewallsocketserver.h @@ -0,0 +1,95 @@ +/* + * 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 = 0); + virtual ~FirewallSocketServer(); + + void AddRule(bool allow, FirewallProtocol p = FP_ANY, FirewallDirection d = FD_ANY, const SocketAddress& addr = SocketAddress()); + void ClearRules(); + + bool Check(FirewallProtocol p, FirewallDirection d, const SocketAddress& addr); + + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + 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 addr; + }; + std::vector<Rule> rules_; +}; + +// 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/third_party/libjingle/files/talk/base/helpers.cc b/third_party/libjingle/files/talk/base/helpers.cc new file mode 100644 index 0000000..58b5485 --- /dev/null +++ b/third_party/libjingle/files/talk/base/helpers.cc @@ -0,0 +1,149 @@ +/* + * 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" +#include "talk/base/time.h" +#include <cstdlib> +#include <cassert> + +// TODO: Change this implementation to use OpenSSL's RAND_bytes. That will +// give cryptographically random values on all platforms. + +#ifdef WIN32 +#include <time.h> +#include <windows.h> +#include <wincrypt.h> +#endif + +namespace cricket { + +static long g_seed = 1L; + +int GetRandom() { + return ((g_seed = g_seed * 214013L + 2531011L) >> 16) & 0x7fff; +} + +static bool s_initrandom; + +long GetRandomSeed() { + return g_seed; +} + +void SetRandomSeed(unsigned long seed) +{ + s_initrandom = true; + g_seed = (long)seed; +} + +void InitRandom(const char *client_unique, size_t len) { + // Hash this string - unique per client + + uint32 hash = 0; + if (client_unique != NULL) { + for (int i = 0; i < (int)len; i++) + hash = ((hash << 2) + hash) + client_unique[i]; + } + + // Now initialize the seed against a high resolution + // counter + + unsigned long seed = GetRandomSeed(); + +#ifdef WIN32 + bool success = false; + HCRYPTPROV hProv = NULL; + if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, + CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { + success = (FALSE != CryptGenRandom(hProv, + sizeof(seed), + reinterpret_cast<BYTE*>(&seed))); + CryptReleaseContext(hProv, 0); + } + + if (!success) { + LARGE_INTEGER big; + QueryPerformanceCounter(&big); + seed = big.LowPart; + } +#else + seed = talk_base::Time(); +#endif + + SetRandomSeed(seed ^ hash); +} + +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', '+', '/' +}; + +// Generates a random string of the given length. We generate base64 values so +// that they will be printable, though that's not necessary. + +std::string CreateRandomString(int len) { + // Random number generator should of been initialized! + assert(s_initrandom); + if (!s_initrandom) + InitRandom(0, 0); + + std::string str; + for (int i = 0; i < len; i++) +#if defined(_MSC_VER) && _MSC_VER < 1300 + str.insert(str.end(), BASE64[GetRandom() & 63]); +#else + str.push_back(BASE64[GetRandom() & 63]); +#endif + return str; +} + +uint32 CreateRandomId() { + uint8 b1 = (uint8)(GetRandom() & 255); + uint8 b2 = (uint8)(GetRandom() & 255); + uint8 b3 = (uint8)(GetRandom() & 255); + uint8 b4 = (uint8)(GetRandom() & 255); + return b1 | (b2 << 8) | (b3 << 16) | (b4 << 24); +} + +bool IsBase64Char(char ch) { + return (('A' <= ch) && (ch <= 'Z')) || + (('a' <= ch) && (ch <= 'z')) || + (('0' <= ch) && (ch <= '9')) || + (ch == '+') || (ch == '/'); +} + +bool IsBase64Encoded(const std::string& str) { + for (size_t i = 0; i < str.size(); ++i) { + if (!IsBase64Char(str.at(i))) + return false; + } + return true; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/base/helpers.h b/third_party/libjingle/files/talk/base/helpers.h new file mode 100644 index 0000000..696edbe --- /dev/null +++ b/third_party/libjingle/files/talk/base/helpers.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 __HELPERS_H__ +#define __HELPERS_H__ + +#include "talk/base/basictypes.h" +#include <string> + +namespace cricket { + +// srand initializer +void InitRandom(const char *client_unique, size_t len); + +// For testing, the random seed can be directly accessed. +long GetRandomSeed(); +void SetRandomSeed(unsigned long seed); + +// Generates a (cryptographically) random string of the given length. +std::string CreateRandomString(int length); + +// Generates a random id +uint32 CreateRandomId(); + +// Determines whether the given string consists entirely of valid base64 +// encoded characters. +bool IsBase64Encoded(const std::string& str); + +} // namespace cricket + +#endif // __HELPERS_H__ diff --git a/third_party/libjingle/files/talk/base/host.cc b/third_party/libjingle/files/talk/base/host.cc new file mode 100644 index 0000000..df5f7f7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/host.cc @@ -0,0 +1,100 @@ +/* + * 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 <cassert> +#include <errno.h> + +#ifdef POSIX +extern "C" { +#include <sys/utsname.h> +} +#endif // POSIX + +#include "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; + using ::exit; +} +#endif + +namespace talk_base { + +namespace { + +void FatalError(const std::string& name, int err) { + PLOG(LERROR, err) << name; + std::exit(1); +} + +} + +#ifdef POSIX +std::string GetHostName() { + struct utsname nm; + if (uname(&nm) < 0) + FatalError("uname", errno); + return std::string(nm.nodename); +} +#endif + +#ifdef WIN32 +std::string GetHostName() { + // TODO: fix this + return "cricket"; +} +#endif + +// Records information about the local host. +Host* gLocalHost = 0; + +const Host& LocalHost() { + if (!gLocalHost) { + std::vector<Network*>* networks = new std::vector<Network*>; + NetworkManager::CreateNetworks(*networks); +#ifdef WIN32 + // This is sort of problematic... one part of the code (the unittests) wants + // 127.0.0.1 to be present and another part (port allocators) don't. Right + // now, they use different APIs, so we can have different behavior. But + // there is something wrong with this. + networks->push_back(new Network("localhost", + SocketAddress::StringToIP("127.0.0.1"))); +#endif + gLocalHost = new Host(GetHostName(), networks); + assert(gLocalHost->networks().size() > 0); + } + + return *gLocalHost; +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/host.h b/third_party/libjingle/files/talk/base/host.h new file mode 100644 index 0000000..7ee603d --- /dev/null +++ b/third_party/libjingle/files/talk/base/host.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_HOST_H__ +#define TALK_BASE_HOST_H__ + +#include <string> +#include <vector> +#include "talk/base/network.h" + +namespace talk_base { + +// Provides information about a host in the network. +class Host { +public: + Host(const std::string& name, std::vector<Network*>* networks) + : name_(name), networks_(networks) { } + + const std::string& name() const { return name_; } + const std::vector<Network*>& networks() const { return *networks_; } + +private: + std::string name_; + std::vector<Network*>* networks_; +}; + +// Returns a reference to the description of the local host. +const Host& LocalHost(); + +// Returns the name of the local host. +std::string GetHostName(); + +} // namespace talk_base + +#endif // TALK_BASE_HOST_H__ diff --git a/third_party/libjingle/files/talk/base/httpbase.cc b/third_party/libjingle/files/talk/base/httpbase.cc new file mode 100644 index 0000000..06b7378 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpbase.cc @@ -0,0 +1,591 @@ +/* + * 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 OSX +#include <errno.h> +#endif + +#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" + +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); +} + +////////////////////////////////////////////////////////////////////// +// HttpParser +////////////////////////////////////////////////////////////////////// + +HttpParser::HttpParser() { + reset(); +} + +HttpParser::~HttpParser() { +} + +void +HttpParser::reset() { + state_ = ST_LEADER; + chunked_ = false; + data_size_ = SIZE_UNKNOWN; +} + +bool +HttpParser::process(const char* buffer, size_t len, size_t& processed, + HttpError& err) { + processed = 0; + err = HE_NONE; + + if (state_ >= ST_COMPLETE) { + ASSERT(false); + return false; + } + + 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; + } + if (!process_line(line, len, err)) { + return false; // no more processing + } + } else if (data_size_ == 0) { + if (chunked_) { + state_ = ST_CHUNKTERM; + } else { + return false; + } + } 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; + err = onHttpRecvData(buffer + processed, available, read); + if (err != HE_NONE) { + return false; // error occurred + } + processed += read; + if (data_size_ != SIZE_UNKNOWN) { + data_size_ -= read; + } + } + } + + return true; +} + +bool +HttpParser::process_line(const char* line, size_t len, HttpError& err) { + switch (state_) { + case ST_LEADER: + state_ = ST_HEADERS; + err = onHttpRecvLeader(line, len); + break; + + case ST_HEADERS: + if (len > 0) { + const char* value = strchrn(line, len, ':'); + if (!value) { + err = HE_PROTOCOL; + break; + } + 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)) { + if (sscanf(value, "%d", &data_size_) != 1) { + err = HE_PROTOCOL; + break; + } + } 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 { + err = HE_PROTOCOL; + break; + } + } + err = onHttpRecvHeader(line, nlen, value, vlen); + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + err = onHttpRecvHeaderComplete(chunked_, data_size_); + } + break; + + case ST_CHUNKSIZE: + if (len > 0) { + char* ptr = NULL; + data_size_ = strtoul(line, &ptr, 16); + if (ptr != line + len) { + err = HE_PROTOCOL; + break; + } + state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA; + } else { + err = HE_PROTOCOL; + } + break; + + case ST_CHUNKTERM: + if (len > 0) { + err = HE_PROTOCOL; + } else { + state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA; + } + break; + + case ST_TRAILERS: + if (len == 0) { + return false; + } + // err = onHttpRecvTrailer(); + break; + + default: + break; + } + + return (err == HE_NONE); +} + +void +HttpParser::end_of_input() { + if ((state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN)) { + complete(HE_NONE); + } else { + complete(HE_DISCONNECTED); + } +} + +void +HttpParser::complete(HttpError err) { + if (state_ < ST_COMPLETE) { + state_ = ST_COMPLETE; + onHttpRecvComplete(err); + } +} + +////////////////////////////////////////////////////////////////////// +// HttpBase +////////////////////////////////////////////////////////////////////// + +HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL), + stream_(NULL) { +} + +HttpBase::~HttpBase() { +} + +bool +HttpBase::isConnected() const { + return (stream_ != NULL) && (stream_->GetState() == SS_OPEN); +} + +bool +HttpBase::attach(StreamInterface* stream) { + if ((mode_ != HM_NONE) || (stream_ != NULL) || (stream == NULL)) { + ASSERT(false); + return false; + } + stream_ = stream; + stream_->SignalEvent.connect(this, &HttpBase::OnEvent); + mode_ = (stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE; + return true; +} + +StreamInterface* +HttpBase::detach() { + if (mode_ != HM_NONE) { + ASSERT(false); + return NULL; + } + StreamInterface* stream = stream_; + stream_ = NULL; + if (stream) { + stream->SignalEvent.disconnect(this); + } + return stream; +} + +/* +bool +HttpBase::accept(PNSocket& socket) { + if (mode_ != HM_NONE) { + ASSERT(false); + return false; + } + + return socket.accept(stream_); +} + +void +HttpBase::connect(const SocketAddress& addr) { + if (mode_ != HM_NONE) { + ASSERT(false); + return; + } + + mode_ = HM_CONNECT; + + SocketAddress local; + if (!stream_.connect(local, addr) && !stream_.isBlocking()) { + onSocketConnect(&stream_, stream_.getError()); + } +} +*/ +void +HttpBase::send(HttpData* data) { + if (mode_ != HM_NONE) { + ASSERT(false); + return; + } else if (!isConnected()) { + OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_SEND; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + 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(); + queue_headers(); + + OnEvent(stream_, SE_WRITE, 0); +} + +void +HttpBase::recv(HttpData* data) { + if (mode_ != HM_NONE) { + ASSERT(false); + return; + } else if (!isConnected()) { + OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED); + return; + } + + mode_ = HM_RECV; + data_ = data; + len_ = 0; + ignore_data_ = chunk_data_ = false; + + reset(); + OnEvent(stream_, SE_READ, 0); +} + +void +HttpBase::abort(HttpError err) { + if (mode_ != HM_NONE) { + if (stream_ != NULL) { + stream_->Close(); + } + do_complete(err); + } +} + +void +HttpBase::flush_data() { + while (true) { + for (size_t start = 0; start < len_; ) { + size_t written; + int error; + StreamResult result = stream_->Write(buffer_ + start, len_ - start, + &written, &error); + if (result == SR_SUCCESS) { + //LOG_F(LS_INFO) << "wrote " << res << " bytes"; + start += written; + continue; + } else if (result == SR_BLOCK) { + //LOG_F(LS_INFO) << "blocking"; + len_ -= start; + memmove(buffer_, buffer_ + start, len_); + return; + } else { + ASSERT(result == SR_ERROR); + LOG_F(LS_ERROR) << "error"; + OnEvent(stream_, SE_CLOSE, error); + return; + } + } + len_ = 0; + + // Check for more headers + if (header_ != data_->end()) { + queue_headers(); + continue; + } + + // Check for document data + if (!data_->document.get()) + break; + + size_t offset = 0, reserve = 0; + if (chunk_data_) { + // Reserve 10 characters at the start for 8-byte hex value and \r\n + offset = 10; + // ... and 2 characters at the end for \r\n + reserve = offset + 2; + ASSERT(reserve < sizeof(buffer_)); + } + + int error = 0; + StreamResult result = data_->document->Read(buffer_ + offset, + sizeof(buffer_) - reserve, + &len_, &error); + if (result == SR_SUCCESS) { + if (!chunk_data_) + continue; + + // Prepend the length and append \r\n + sprintfn(buffer_, offset, "%.*x", (offset - 2), len_); + memcpy(buffer_ + offset - 2, "\r\n", 2); + memcpy(buffer_ + offset + len_, "\r\n", 2); + ASSERT(len_ + reserve <= sizeof(buffer_)); + len_ += reserve; + } else if (result == SR_EOS) { + if (!chunk_data_) + break; + + // Append the empty chunk and empty trailers, then turn off chunking. + len_ = sprintfn(buffer_, sizeof(buffer_), "0\r\n\r\n"); + chunk_data_ = false; + } else { + LOG_F(LS_ERROR) << "Read error: " << error; + do_complete(HE_STREAM); + return; + } + } + + do_complete(); +} + +void +HttpBase::queue_headers() { + 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 { + break; + } + } + if (header_ == data_->end()) { + len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n"); + } +} + +void +HttpBase::do_complete(HttpError err) { + ASSERT(mode_ != HM_NONE); + HttpMode mode = mode_; + mode_ = HM_NONE; + data_ = NULL; + if (notify_) { + notify_->onHttpComplete(mode, err); + } +} + +void +HttpBase::OnEvent(StreamInterface* stream, int events, int error) { + 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)) { + // 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; + while (true) { + if (len_ >= sizeof(buffer_)) { + do_complete(HE_OVERFLOW); + return; + } + size_t read; + int error; + StreamResult result = stream_->Read(buffer_ + len_, + sizeof(buffer_) - len_, + &read, &error); + if ((result == SR_BLOCK) || (result == SR_EOS)) + return; + if (result == SR_ERROR) { + OnEvent(stream_, SE_CLOSE, error); + return; + } + ASSERT(result == SR_SUCCESS); + //LOG(INFO) << "HttpBase << " << std::string(buffer_ + len_, res); + len_ += read; + HttpError herr; + bool more = process(buffer_, len_, read, herr); + len_ -= read; + memcpy(buffer_, buffer_ + read, len_); + if (!more) { + complete(herr); + return; + } + if (++loop_count > kMaxReadCount) { + LOG_F(LS_WARNING) << "danger of starvation"; + break; + } + } + return; + } + + if ((events & SE_CLOSE) == 0) + return; + + if (stream_ != NULL) { + stream_->Close(); + } + HttpError herr; + // TODO: Pass through errors instead of translating them? + if (error == 0) { + herr = HE_DISCONNECTED; + } else if (error == SOCKET_EACCES) { + herr = HE_AUTH; + } else if (error == SEC_E_CERT_EXPIRED) { + herr = HE_CERTIFICATE_EXPIRED; + } else { + LOG_F(LS_ERROR) << "SE_CLOSE error: " << error; + herr = HE_SOCKET; + } + if ((mode_ == HM_RECV) && (error == HE_NONE)) { + end_of_input(); + } else if (mode_ != HM_NONE) { + do_complete(mkerr(herr, HE_DISCONNECTED)); + } else if (notify_) { + notify_->onHttpClosed(mkerr(herr, HE_DISCONNECTED)); + } +} + +// +// HttpParser Implementation +// + +HttpError +HttpBase::onHttpRecvLeader(const char* line, size_t len) { + return data_->parseLeader(line, len); +} + +HttpError +HttpBase::onHttpRecvHeader(const char* name, size_t nlen, const char* value, + size_t vlen) { + std::string sname(name, nlen), svalue(value, vlen); + data_->addHeader(sname, svalue); + //LOG(INFO) << sname << ": " << svalue; + return HE_NONE; +} + +HttpError +HttpBase::onHttpRecvHeaderComplete(bool chunked, size_t& data_size) { + return notify_ ? notify_->onHttpHeaderComplete(chunked, data_size) : HE_NONE; +} + +HttpError +HttpBase::onHttpRecvData(const char* data, size_t len, size_t& read) { + if (ignore_data_ || !data_->document.get()) { + read = len; + return HE_NONE; + } + int error = 0; + switch (data_->document->Write(data, len, &read, &error)) { + case SR_SUCCESS: + return HE_NONE; + case SR_EOS: + case SR_BLOCK: + LOG_F(LS_ERROR) << "Write EOS or block"; + return HE_STREAM; + } + LOG_F(LS_ERROR) << "Write error: " << error; + return HE_STREAM; +} + +void +HttpBase::onHttpRecvComplete(HttpError err) { + do_complete(err); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/httpbase.h b/third_party/libjingle/files/talk/base/httpbase.h new file mode 100644 index 0000000..64aefe9 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpbase.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_HTTPBASE_H__ +#define TALK_BASE_HTTPBASE_H__ + +#include "talk/base/httpcommon.h" + +namespace talk_base { + +class StreamInterface; + +////////////////////////////////////////////////////////////////////// +// HttpParser +////////////////////////////////////////////////////////////////////// + +class HttpParser { +public: + HttpParser(); + virtual ~HttpParser(); + + void reset(); + bool process(const char* buffer, size_t len, size_t& read, HttpError& err); + void end_of_input(); + void complete(HttpError err); + +protected: + bool process_line(const char* line, size_t len, HttpError& err); + + // HttpParser Interface + virtual HttpError onHttpRecvLeader(const char* line, size_t len) = 0; + virtual HttpError onHttpRecvHeader(const char* name, size_t nlen, + const char* value, size_t vlen) = 0; + virtual HttpError onHttpRecvHeaderComplete(bool chunked, size_t& data_size) = 0; + virtual HttpError onHttpRecvData(const char* data, size_t len, size_t& read) = 0; + virtual void onHttpRecvComplete(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 HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0; + virtual void onHttpComplete(HttpMode mode, HttpError err) = 0; + virtual void onHttpClosed(HttpError err) = 0; +}; + +////////////////////////////////////////////////////////////////////// +// HttpBase +////////////////////////////////////////////////////////////////////// + +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 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_; } + +protected: + void flush_data(); + void queue_headers(); + void do_complete(HttpError err = HE_NONE); + + void OnEvent(StreamInterface* stream, int events, int error); + + // HttpParser Interface + virtual HttpError onHttpRecvLeader(const char* line, size_t len); + virtual HttpError onHttpRecvHeader(const char* name, size_t nlen, + const char* value, size_t vlen); + virtual HttpError onHttpRecvHeaderComplete(bool chunked, size_t& data_size); + virtual HttpError onHttpRecvData(const char* data, size_t len, size_t& read); + virtual void onHttpRecvComplete(HttpError err); + +private: + enum { kBufferSize = 32 * 1024 }; + + HttpMode mode_; + HttpData* data_; + IHttpNotify* notify_; + StreamInterface* 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/third_party/libjingle/files/talk/base/httpclient.cc b/third_party/libjingle/files/talk/base/httpclient.cc new file mode 100644 index 0000000..b833007 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpclient.cc @@ -0,0 +1,716 @@ +/* + * 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/basicdefs.h" + +namespace talk_base { + +////////////////////////////////////////////////////////////////////// +// Helpers +////////////////////////////////////////////////////////////////////// + +namespace { + +const size_t kCacheHeader = 0; +const size_t kCacheBody = 1; + +std::string HttpAddress(const SocketAddress& address) { + return (address.port() == HTTP_DEFAULT_PORT) + ? address.hostname() : address.ToString(); +} + +// 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 HttpRequestData& request, + const HttpResponseData& response) { + bool verb_allows_cache = (request.verb == HV_GET) + || (request.verb == HV_HEAD); + bool is_range_response = response.hasHeader(HH_CONTENT_RANGE, NULL); + bool has_expires = response.hasHeader(HH_EXPIRES, NULL); + bool request_allows_cache = + has_expires || (std::string::npos != request.path.find('?')); + bool response_allows_cache = + has_expires || HttpCodeIsCacheable(response.scode); + + bool may_cache = verb_allows_cache + && request_allows_cache + && response_allows_cache + && !is_range_response; + + std::string value; + if (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 HttpRequestData& request, + const HttpResponseData& response) { + // Temporaries + std::string s_temp; + unsigned long i_temp; + + // Current time + unsigned long now = time(0); + + HttpAttributeList cache_control; + if (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 (!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 (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 (response.hasHeader(HH_EXPIRES, &s_temp) + && HttpDateToSeconds(s_temp, &i_temp)) { + lifetime = i_temp - date; + } else if (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 SocketAddress& server, + const HttpRequestData& request) { + std::string url; + url.append(ToString(request.verb)); + url.append("_"); + if ((_strnicmp(request.path.c_str(), "http://", 7) == 0) + || (_strnicmp(request.path.c_str(), "https://", 8) == 0)) { + url.append(request.path); + } else { + url.append("http://"); + url.append(HttpAddress(server)); + url.append(request.path); + } + return url; +} + +} // anonymous namespace + +////////////////////////////////////////////////////////////////////// +// HttpClient +////////////////////////////////////////////////////////////////////// + +HttpClient::HttpClient(const std::string& agent, StreamPool* pool) +: agent_(agent), pool_(pool), fail_redirect_(false), absolute_uri_(false), + cache_(NULL), cache_state_(CS_READY) +{ + base_.notify(this); +} + +HttpClient::~HttpClient() { + base_.notify(NULL); + base_.abort(HE_SHUTDOWN); + release(); +} + +void HttpClient::reset() { + server_.Clear(); + request_.clear(true); + response_.clear(true); + context_.reset(); + 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_), true); +} + +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; + } + + // If no content has been specified, using length of 0. + request_.setHeader(HH_CONTENT_LENGTH, "0", false); + + request_.setHeader(HH_USER_AGENT, agent_, false); + request_.setHeader(HH_CONNECTION, "Keep-Alive", false); + if (_strnicmp(request_.path.c_str(), "http", 4) == 0) { + request_.setHeader(HH_PROXY_CONNECTION, "Keep-Alive", false); + } + + bool absolute_uri = absolute_uri_; + if (PROXY_HTTPS == proxy_.type) { + request().version = HVER_1_0; + // Proxies require canonical form + absolute_uri = true; + } + + // Convert to canonical form (if not already) + if (absolute_uri && (_strnicmp(request().path.c_str(), "http://", 7) != 0)) { + std::string canonical_path("http://"); + canonical_path.append(HttpAddress(server_)); + canonical_path.append(request().path); + request().path = canonical_path; + } + + if ((NULL != cache_) && CheckCache()) { + return; + } + + int stream_err; + StreamInterface* stream = pool_->RequestConnectedStream(server_, &stream_err); + if (stream == NULL) { + if (stream_err) + LOG(LS_ERROR) << "RequestConnectedStream returned: " << stream_err; + onHttpComplete(HM_CONNECT, (stream_err == 0) ? HE_NONE : HE_SOCKET); + } else { + base_.attach(stream); + if (stream->GetState() == SS_OPEN) { + base_.send(&request_); + } + } +} + +void HttpClient::prepare_get(const std::string& url) { + reset(); + Url<char> purl(url); + set_server(SocketAddress(purl.server(), purl.port(), false)); + 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.server(), purl.port(), false)); + 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::BeginCacheFile() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(server_, 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; + } + + // 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; + std::string formatted_header(it->first); + formatted_header.append(": "); + formatted_header.append(it->second); + formatted_header.append("\r\n"); + StreamResult result = stream->WriteAll(formatted_header.data(), + formatted_header.length(), + NULL, NULL); + if (SR_SUCCESS != result) { + 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(server_, request_)); + } +} + +bool HttpClient::CheckCache() { + ASSERT(NULL != cache_); + ASSERT(CS_READY == cache_state_); + + std::string id = GetCacheID(server_, request_); + if (!cache_->HasResource(id)) { + // No cache file available + return false; + } + + HttpError error = ReadCacheHeaders(id, true); + + if (HE_NONE == error) { + switch (HttpGetCacheState(request_, response_)) { + 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; + + while (true) { + std::string formatted_header; + StreamResult result = stream->ReadLine(&formatted_header); + if (SR_EOS == result) + break; + + if (SR_SUCCESS != result) { + LOG_F(LS_ERROR) << "ReadLine error in cache headers"; + return HE_CACHE; + } + 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); + } + + 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->GetSize(&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(server_, 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 (!ignore_data && !chunked && response_.document.get()) { + // Attempt to pre-allocate space for the downloaded data. + if (!response_.document->ReserveSize(data_size)) { + return HE_OVERFLOW; + } + } + SignalHeaderAvailable(this, chunked, data_size); + 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(server_, 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 ((HttpCodeIsRedirection(response_.scode) && !fail_redirect_) + || ((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(request_, response_)) { + if (BeginCacheFile()) { + cache_state_ = CS_WRITING; + } + } + return HE_NONE; +} + +void HttpClient::onHttpComplete(HttpMode mode, HttpError err) { + if (err != HE_NONE) { + // fall through + } else if (mode == HM_CONNECT) { + base_.send(&request_); + return; + } else if ((mode == HM_SEND) || HttpCodeIsInformational(response_.scode)) { + // If you're interested in informational headers, catch + // SignalHeaderAvailable. + base_.recv(&response_); + return; + } else { + if (!HttpShouldKeepAlive(response_)) { + LOG(INFO) << "HttpClient: closing socket"; + base_.stream()->Close(); + } + if (HttpCodeIsRedirection(response_.scode) && !fail_redirect_) { + std::string value; + if (!response_.hasHeader(HH_LOCATION, &value)) { + err = HE_PROTOCOL; + } else { + Url<char> purl(value); + set_server(SocketAddress(purl.server(), purl.port(), false)); + 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. + err = HE_STREAM; + } + } + if (err == HE_NONE) { + context_.reset(); + response_.clear(false); + release(); + start(); + return; + } + } else if ((HC_PROXY_AUTHENTICATION_REQUIRED == response_.scode) + && (PROXY_HTTPS == proxy_.type)) { + std::string response, 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, response, auth_method); + context_.reset(context); + if (res == HAR_RESPONSE) { + request_.setHeader(HH_PROXY_AUTHORIZATION, response); + 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) { + SignalHttpClientClosed(this, err); +} + +////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/httpclient.h b/third_party/libjingle/files/talk/base/httpclient.h new file mode 100644 index 0000000..789cb59 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpclient.h @@ -0,0 +1,155 @@ +/* + * 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 { + +////////////////////////////////////////////////////////////////////// +// HttpClient +////////////////////////////////////////////////////////////////////// + +class DiskCache; +class HttpClient; +class IPNetPool; + +class HttpClient : private IHttpNotify { +public: + HttpClient(const std::string& agent, StreamPool* pool); + virtual ~HttpClient(); + + void set_pool(StreamPool* pool) { pool_ = pool; } + + const std::string& agent() const { return agent_; } + + void set_proxy(const ProxyInfo& proxy) { proxy_ = proxy; } + const ProxyInfo& proxy() const { return proxy_; } + + void set_fail_redirect(bool fail_redirect) { fail_redirect_ = fail_redirect; } + bool fail_redirect() const { return fail_redirect_; } + + void use_absolute_uri(bool absolute_uri) { absolute_uri_ = absolute_uri; } + bool absolute_uri() const { return absolute_uri_; } + + 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_; } + + HttpRequestData& request() { return request_; } + const HttpRequestData& request() const { return request_; } + HttpResponseData& response() { return response_; } + const HttpResponseData& response() const { return 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); + + // After you finish setting up your request, call start. + void start(); + + // Signalled when the header has finished downloading, before the document + // content is processed. This notification is for informational purposes + // only. Do not modify the client in response to this. + sigslot::signal3<const HttpClient*,bool,size_t> SignalHeaderAvailable; + // Signalled when the current 'call' finishes. On success, err is 0. + sigslot::signal2<HttpClient*,int> SignalHttpClientComplete; + // Signalled when the network connection goes down while a call is not + // in progress. + sigslot::signal2<HttpClient*,int> SignalHttpClientClosed; + +protected: + void release(); + + 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_; + HttpRequestData request_; + HttpResponseData response_; + bool fail_redirect_, absolute_uri_; + scoped_ptr<HttpAuthContext> context_; + DiskCache* cache_; + CacheState cache_state_; +}; + +////////////////////////////////////////////////////////////////////// +// Default implementation of HttpClient +////////////////////////////////////////////////////////////////////// + +class HttpClientDefault : public ReuseSocketPool, public HttpClient { +public: + HttpClientDefault(SocketFactory* factory, const std::string& agent) + : ReuseSocketPool(factory), HttpClient(agent, NULL) + { + set_pool(this); + } +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_HTTPCLIENT_H__ diff --git a/third_party/libjingle/files/talk/base/httpcommon-inl.h b/third_party/libjingle/files/talk/base/httpcommon-inl.h new file mode 100644 index 0000000..5235b89 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpcommon-inl.h @@ -0,0 +1,114 @@ +/* + * 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> +Url<CTYPE>::Url(const string& url) { + const CTYPE* raw_url = url.c_str(); + if (ascnicmp(raw_url, "http://", 7) == 0) { + raw_url += 7; + m_secure = false; + } else if (ascnicmp(raw_url, "https://", 8) == 0) { + raw_url += 8; + m_secure = true; + } else { + return; + } + m_port = UrlDefaultPort(m_secure); + const CTYPE* colon = ::strchr(raw_url, static_cast<CTYPE>(':')); + const CTYPE* slash = ::strchr(raw_url, static_cast<CTYPE>('/')); + if (!colon && !slash) { + m_server = url; + // TODO: rethink this slash + m_path.append(1, static_cast<CTYPE>('/')); + } else { + const CTYPE* ptr; + if (colon == 0) { + ptr = slash; + } else if (slash == 0) { + ptr = colon; + } else { + ptr = _min(colon, slash); + } + m_server.assign(raw_url, ptr - raw_url); + if (ptr == colon) { + CTYPE* tmp = 0; + m_port = static_cast<uint16>(::strtoul(ptr + 1, &tmp, 10)); + ptr = tmp; + } + const CTYPE* query = ::strchr(ptr, static_cast<CTYPE>('?')); + if (!query) { + m_path.assign(ptr); + } else { + m_path.assign(ptr, query - ptr); + m_query.assign(query); + } + } + ASSERT(m_path.empty() || (m_path[0] == static_cast<CTYPE>('/'))); + ASSERT(m_query.empty() || (m_query[0] == static_cast<CTYPE>('?'))); +} + +template<class CTYPE> +typename Traits<CTYPE>::string Url<CTYPE>::full_path() { + string full_path(m_path); + full_path.append(m_query); + return full_path; +} + +template<class CTYPE> +typename Traits<CTYPE>::string Url<CTYPE>::url() { + CTYPE protocol[9]; + asccpyn(protocol, ARRAY_SIZE(protocol), m_secure ? "https://" : "http://"); + string url(protocol); + url.append(m_server); + if (m_port != UrlDefaultPort(m_secure)) { + CTYPE format[5], port[32]; + asccpyn(format, ARRAY_SIZE(format), ":%hu"); + sprintfn(port, ARRAY_SIZE(port), format, m_port); + url.append(port); + } + url.append(m_path); + url.append(m_query); + return url; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_HTTPCOMMON_INL_H__ diff --git a/third_party/libjingle/files/talk/base/httpcommon.cc b/third_party/libjingle/files/talk/base/httpcommon.cc new file mode 100644 index 0000000..0358121 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpcommon.cc @@ -0,0 +1,940 @@ +/* + * 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 <winsock2.h> +#include <ws2tcpip.h> +#define _WINSOCKAPI_ +#include <windows.h> +#define SECURITY_WIN32 +#include <security.h> +#endif + +#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/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" +}; +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-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:?? + 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; +} + +} // anonymous namespace + +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; + } +#ifdef OSX + tm *tm_for_timezone = localtime((time_t *)&gmt); + *seconds = gmt + tm_for_timezone->tm_gmtoff; +#else + *seconds = gmt - timezone; +#endif + return true; +} + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +void +HttpData::clear(bool release_document) { + if (release_document) { + document.reset(); + } + m_headers.clear(); +} + +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) { + m_headers.erase(name); + combine = HC_NO; + } + // At this point, combine is one of (YES, NO, NEW) + if (combine != HC_NO) { + HeaderMap::iterator it = m_headers.find(name); + if (it != m_headers.end()) { + if (combine == HC_YES) { + it->second.append(","); + it->second.append(value); + } + return; + } + } + m_headers.insert(HeaderMap::value_type(name, value)); +} + +void +HttpData::clearHeader(const std::string& name) { + m_headers.erase(name); +} + +bool +HttpData::hasHeader(const std::string& name, std::string* value) const { + HeaderMap::const_iterator it = m_headers.find(name); + if (it == m_headers.end()) { + return false; + } else if (value) { + *value = it->second; + } + return true; +} + +void +HttpData::setContent(const std::string& content_type, + StreamInterface* document) { + ASSERT(document != NULL); + this->document.reset(document); + setHeader(HH_CONTENT_TYPE, content_type); + size_t content_length = 0; + if (this->document->GetSize(&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) { + HttpData::clear(release_document); + verb = HV_GET; + path.clear(); +} + +size_t +HttpRequestData::formatLeader(char* buffer, size_t size) { + 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); + uint32 vmajor, vminor; + int vend, dstart, dend; + if ((sscanf(line, "%*s%n %n%*s%n HTTP/%lu.%lu", &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; +} + +// +// HttpResponseData +// + +void +HttpResponseData::clear(bool release_document) { + HttpData::clear(release_document); + scode = HC_INTERNAL_SERVER_ERROR; + message.clear(); +} + +void +HttpResponseData::set_success(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0"); +} + +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"); +} + +void +HttpResponseData::set_error(uint32 scode) { + this->scode = scode; + message.clear(); + setHeader(HH_CONTENT_LENGTH, "0"); +} + +size_t +HttpResponseData::formatLeader(char* buffer, size_t size) { + 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; + uint32 vmajor, vminor; + if ((sscanf(line, "HTTP/%lu.%lu %lu%n", &vmajor, &vminor, &scode, &pos) != 3) + || (vmajor != 1)) { + return HE_PROTOCOL; + } + if (vminor == 0) { + version = HVER_1_0; + } else if (vminor == 1) { + version = HVER_1_1; + } else { + return HE_PROTOCOL; + } + 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 + +static 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", 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 decoded_challenge; + HttpHasNthAttribute(args, 1, &decoded_challenge, NULL); + decoded_challenge = Base64::decode(decoded_challenge); + if (!decoded_challenge.empty()) { + 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 @ " << TimeDiff(talk_base::Time(), 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 @ " << TimeDiff(talk_base::Time(), 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 @ " << TimeDiff(talk_base::Time(), 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 @ " << TimeDiff(talk_base::Time(), now); + LOG(LS_VERBOSE) << "CompleteAuthToken returned: " + << ErrorName(ret, SECURITY_ERRORS); + if (FAILED(ret)) { + return HAR_ERROR; + } + } + + //LOG(INFO) << "$$$ NEGOTIATE took " << TimeDiff(talk_base::Time(), 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/third_party/libjingle/files/talk/base/httpcommon.h b/third_party/libjingle/files/talk/base/httpcommon.h new file mode 100644 index 0000000..2893933 --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpcommon.h @@ -0,0 +1,373 @@ +/* + * 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/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 +}; + +enum HttpVersion { + HVER_1_0, HVER_1_1, + HVER_LAST = HVER_1_1 +}; + +enum HttpVerb { + HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD, + HV_LAST = HV_HEAD +}; + +enum HttpError { + HE_NONE, + HE_PROTOCOL, HE_DISCONNECTED, HE_OVERFLOW, + HE_SOCKET, HE_SHUTDOWN, HE_OPERATION_CANCELLED, + 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_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 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 const uint16 UrlDefaultPort(bool secure) { + return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT; +} + +// 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); + } +}; + +////////////////////////////////////////////////////////////////////// +// 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); + Url(const string& path, const string& server, uint16 port = HTTP_DEFAULT_PORT) + : m_server(server), m_path(path), m_port(port), + m_secure(HTTP_SECURE_PORT == port) + { + ASSERT(m_path.empty() || (m_path[0] == static_cast<CTYPE>('/'))); + } + + bool valid() const { return !m_server.empty(); } + const string& server() const { return m_server; } + // Note: path() was renamed to path_, because it now uses the stricter sense + // of not including a query string. I'm trying to think of a clearer name. + const string& path_() const { return m_path; } + const string& query() const { return m_query; } + string full_path(); + string url(); + uint16 port() const { return m_port; } + bool secure() const { return m_secure; } + + void set_server(const string& val) { m_server = val; } + void set_path(const string& val) { + ASSERT(val.empty() || (val[0] == static_cast<CTYPE>('/'))); + m_path = val; + } + void set_query(const string& val) { + ASSERT(val.empty() || (val[0] == static_cast<CTYPE>('?'))); + m_query = val; + } + void set_port(uint16 val) { m_port = val; } + void set_secure(bool val) { m_secure = val; } + +private: + string m_server, m_path, m_query; + uint16 m_port; + bool m_secure; +}; + +////////////////////////////////////////////////////////////////////// +// HttpData +////////////////////////////////////////////////////////////////////// + +struct HttpData { + typedef std::multimap<std::string, std::string, iless> HeaderMap; + typedef HeaderMap::const_iterator const_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); + } + void clearHeader(const std::string& name); + + // 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 m_headers.begin(); + } + inline const_iterator end() const { + return m_headers.end(); + } + inline const_iterator begin(const std::string& name) const { + return m_headers.lower_bound(name); + } + inline const_iterator end(const std::string& name) const { + return m_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 m_headers.lower_bound(ToString(header)); + } + inline const_iterator end(HttpHeader header) const { + return m_headers.upper_bound(ToString(header)); + } + + void setContent(const std::string& content_type, StreamInterface* document); + + virtual size_t formatLeader(char* buffer, size_t size) = 0; + virtual HttpError parseLeader(const char* line, size_t len) = 0; + +protected: + virtual ~HttpData() { } + void clear(bool release_document); + +private: + HeaderMap m_headers; +}; + +struct HttpRequestData : public HttpData { + HttpVerb verb; + std::string path; + + HttpRequestData() : verb(HV_GET) { } + + void clear(bool release_document); + + virtual size_t formatLeader(char* buffer, size_t size); + virtual HttpError parseLeader(const char* line, size_t len); +}; + +struct HttpResponseData : public HttpData { + uint32 scode; + std::string message; + + HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { } + void clear(bool release_document); + + // 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); + virtual HttpError parseLeader(const char* line, size_t len); +}; + +////////////////////////////////////////////////////////////////////// +// 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/third_party/libjingle/files/talk/base/httpserver.cc b/third_party/libjingle/files/talk/base/httpserver.cc new file mode 100644 index 0000000..9c27b5c --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpserver.cc @@ -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. + */ + +#include <algorithm> + +#include "talk/base/httpcommon-inl.h" + +#include "talk/base/asyncsocket.h" +#include "talk/base/common.h" +#include "talk/base/httpserver.h" +#include "talk/base/logging.h" +#include "talk/base/socketstream.h" + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::HttpServer() : next_connection_id_(1) { +} + +HttpServer::~HttpServer() { + for (ConnectionMap::iterator it = connections_.begin(); + it != connections_.end(); + ++it) { + StreamInterface* stream = it->second->EndProcess(); + delete stream; + delete it->second; + } +} + +int +HttpServer::HandleConnection(StreamInterface* stream) { + int connection_id = next_connection_id_++; + ASSERT(connection_id != HTTP_INVALID_CONNECTION_ID); + Connection* connection = new Connection(connection_id, this); + connections_.insert(ConnectionMap::value_type(connection_id, connection)); + connection->BeginProcess(stream); + return connection_id; +} + +void +HttpServer::Respond(HttpTransaction* transaction) { + int connection_id = transaction->connection_id(); + if (Connection* connection = Find(connection_id)) { + connection->Respond(transaction); + } else { + delete transaction; + // We may be tempted to SignalHttpComplete, but that implies that a + // connection still exists. + } +} + +void +HttpServer::Close(int connection_id, bool force) { + if (Connection* connection = Find(connection_id)) { + connection->InitiateClose(force); + } +} + +void +HttpServer::CloseAll(bool force) { + std::list<Connection*> connections; + for (ConnectionMap::const_iterator it = connections_.begin(); + it != connections_.end(); ++it) { + connections.push_back(it->second); + } + for (std::list<Connection*>::const_iterator it = connections.begin(); + it != connections.end(); ++it) { + (*it)->InitiateClose(force); + } +} + +HttpServer::Connection* +HttpServer::Find(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) + return NULL; + return it->second; +} + +void +HttpServer::Remove(int connection_id) { + ConnectionMap::iterator it = connections_.find(connection_id); + if (it == connections_.end()) { + ASSERT(false); + return; + } + Connection* connection = it->second; + connections_.erase(it); + SignalConnectionClosed(this, connection_id, connection->EndProcess()); + delete connection; +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpServer::Connection +/////////////////////////////////////////////////////////////////////////////// + +HttpServer::Connection::Connection(int connection_id, HttpServer* server) + : connection_id_(connection_id), server_(server), + current_(NULL), signalling_(false), close_(false) { +} + +HttpServer::Connection::~Connection() { + delete current_; +} + +void +HttpServer::Connection::BeginProcess(StreamInterface* stream) { + base_.notify(this); + base_.attach(stream); + current_ = new HttpTransaction(connection_id_); + current_->request()->document.reset(new MemoryStream); + if (base_.mode() != HM_CONNECT) + base_.recv(current_->request()); +} + +StreamInterface* +HttpServer::Connection::EndProcess() { + base_.notify(NULL); + base_.abort(HE_DISCONNECTED); + return base_.detach(); +} + +void +HttpServer::Connection::Respond(HttpTransaction* transaction) { + ASSERT(current_ == NULL); + current_ = transaction; + if (current_->response()->begin() == current_->response()->end()) { + current_->response()->set_error(HC_INTERNAL_SERVER_ERROR); + } + bool keep_alive = HttpShouldKeepAlive(*transaction->request()); + current_->response()->setHeader(HH_CONNECTION, + keep_alive ? "Keep-Alive" : "Close", + false); + close_ = !HttpShouldKeepAlive(*transaction->response()); + base_.send(current_->response()); +} + +void +HttpServer::Connection::InitiateClose(bool force) { + if (!signalling_ && (force || (base_.mode() != HM_SEND))) { + server_->Remove(connection_id_); + } else { + close_ = true; + } +} + +// +// IHttpNotify Implementation +// + +HttpError +HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) { + if (data_size == SIZE_UNKNOWN) { + data_size = 0; + } + return HE_NONE; +} + +void +HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) { + if (mode == HM_SEND) { + ASSERT(current_ != NULL); + signalling_ = true; + server_->SignalHttpRequestComplete(server_, current_, err); + signalling_ = false; + if (close_) { + // Force a close + err = HE_DISCONNECTED; + } + } + if (err != HE_NONE) { + server_->Remove(connection_id_); + } else if (mode == HM_CONNECT) { + base_.recv(current_->request()); + } else if (mode == HM_RECV) { + ASSERT(current_ != NULL); + // TODO: do we need this? + //request_.document_->rewind(); + HttpTransaction* transaction = current_; + current_ = NULL; + server_->SignalHttpRequest(server_, transaction); + } else if (mode == HM_SEND) { + current_->request()->clear(true); + current_->request()->document.reset(new MemoryStream); + current_->response()->clear(true); + base_.recv(current_->request()); + } else { + ASSERT(false); + } +} + +void +HttpServer::Connection::onHttpClosed(HttpError err) { + UNUSED(err); + server_->Remove(connection_id_); +} + +/////////////////////////////////////////////////////////////////////////////// +// HttpListenServer +/////////////////////////////////////////////////////////////////////////////// + +HttpListenServer::HttpListenServer(AsyncSocket* listener) + : listener_(listener) { + listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent); +} + +HttpListenServer::~HttpListenServer() { +} + +int +HttpListenServer::Listen(const SocketAddress& address) { + if ((listener_->Bind(address) != SOCKET_ERROR) && + (listener_->Listen(5) != SOCKET_ERROR)) + return 0; + return listener_->GetError(); +} + +bool +HttpListenServer::GetAddress(SocketAddress& address) { + address = listener_->GetLocalAddress(); + return !address.IsNil(); +} + +void +HttpListenServer::OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == listener_); + AsyncSocket* incoming = static_cast<AsyncSocket*>(listener_->Accept(NULL)); + if (incoming) + HandleConnection(new SocketStream(incoming)); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/httpserver.h b/third_party/libjingle/files/talk/base/httpserver.h new file mode 100644 index 0000000..a09218c --- /dev/null +++ b/third_party/libjingle/files/talk/base/httpserver.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_HTTPSERVER_H__ +#define TALK_BASE_HTTPSERVER_H__ + +#include <map> +#include "talk/base/httpbase.h" + +namespace talk_base { + +class AsyncSocket; +class HttpServer; +class SocketAddress; + +////////////////////////////////////////////////////////////////////// +// HttpServer +////////////////////////////////////////////////////////////////////// + +const int HTTP_INVALID_CONNECTION_ID = 0; + +class HttpTransaction { +public: + HttpTransaction(int connection_id) : connection_id_(connection_id) { } + ~HttpTransaction() { } + + int connection_id() const { return connection_id_; } + + HttpRequestData* request() { return &request_; } + HttpResponseData* response() { return &response_; } + +private: + int connection_id_; + HttpRequestData request_; + HttpResponseData response_; +}; + +class HttpServer { +public: + HttpServer(); + virtual ~HttpServer(); + + int HandleConnection(StreamInterface* stream); + // Due to sigslot issues, we can't destroy some streams at an arbitrary time. + sigslot::signal3<HttpServer*, int, StreamInterface*> SignalConnectionClosed; + + // An HTTP request has been made, and is available in the transaction object. + // Populate the transaction's response, and then return the object via the + // Respond method. Note that during this time, ownership of the transaction + // object is transferred, so it may be passed between threads, although + // respond must be called on the server's active thread. + sigslot::signal2<HttpServer*, HttpTransaction*> SignalHttpRequest; + void Respond(HttpTransaction* transaction); + + // If you want to know when a request completes, listen to this event. + sigslot::signal3<HttpServer*, HttpTransaction*, int> + SignalHttpRequestComplete; + + // Stop processing the connection indicated by connection_id. + // Unless force is true, the server will complete sending a response that is + // in progress. + void Close(int connection_id, bool force); + void CloseAll(bool force); + +private: + class Connection : private IHttpNotify { + public: + Connection(int connection_id, HttpServer* server); + virtual ~Connection(); + + void BeginProcess(StreamInterface* stream); + StreamInterface* EndProcess(); + + void Respond(HttpTransaction* transaction); + void InitiateClose(bool force); + + // IHttpNotify Interface + virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size); + virtual void onHttpComplete(HttpMode mode, HttpError err); + virtual void onHttpClosed(HttpError err); + + int connection_id_; + HttpServer* server_; + HttpBase base_; + HttpTransaction* current_; + bool signalling_, close_; + }; + + Connection* Find(int connection_id); + void Remove(int connection_id); + + friend class Connection; + typedef std::map<int,Connection*> ConnectionMap; + + ConnectionMap connections_; + int next_connection_id_; +}; + +////////////////////////////////////////////////////////////////////// + +class HttpListenServer : public HttpServer, public sigslot::has_slots<> { +public: + HttpListenServer(AsyncSocket* listener); + virtual ~HttpListenServer(); + + int Listen(const SocketAddress& address); + bool GetAddress(SocketAddress& address); + +private: + void OnReadEvent(AsyncSocket* socket); + + AsyncSocket* listener_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_HTTPSERVER_H__ diff --git a/third_party/libjingle/files/talk/base/linked_ptr.h b/third_party/libjingle/files/talk/base/linked_ptr.h new file mode 100644 index 0000000..8d5ce49 --- /dev/null +++ b/third_party/libjingle/files/talk/base/linked_ptr.h @@ -0,0 +1,115 @@ +/* + * 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/third_party/libjingle/files/talk/base/logging.cc b/third_party/libjingle/files/talk/base/logging.cc new file mode 100644 index 0000000..2a7fbe3 --- /dev/null +++ b/third_party/libjingle/files/talk/base/logging.cc @@ -0,0 +1,349 @@ +/* + * 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 <windows.h> +#define snprintf _snprintf +#undef ERROR // wingdi.h +#endif + +#include <iostream> +#include <iomanip> + +#include "talk/base/logging.h" +#include "talk/base/stream.h" +#include "talk/base/stringencode.h" +#include "talk/base/time.h" + +namespace talk_base { + +///////////////////////////////////////////////////////////////////////////// +// Constant Labels +///////////////////////////////////////////////////////////////////////////// + +const char * FindLabel(int value, const talk_base::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 talk_base::ConstantLabel * err_table) { + const char * value = 0; + 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%08lx", err); + return buffer; +} + +///////////////////////////////////////////////////////////////////////////// +// LogMessage +///////////////////////////////////////////////////////////////////////////// + +#if _DEBUG +static const int LOG_DEFAULT = LS_INFO; +#else // !_DEBUG +static const int LOG_DEFAULT = LogMessage::NO_LOGGING; +#endif // !_DEBUG + +// By default, release builds don't log, debug builds at info level +int LogMessage::min_sev_ = LOG_DEFAULT; +int LogMessage::dbg_sev_ = LOG_DEFAULT; + +// No file logging by default +int LogMessage::stream_sev_ = NO_LOGGING; + +// Don't bother printing context for the ubiquitous INFO log messages +int LogMessage::ctx_sev_ = LS_WARNING; + +// stream_ defaults to NULL +// 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). +StreamInterface* LogMessage::stream_; + +// 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 to 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) { + if (timestamp_) { + uint32 time = TimeDiff(Time(), 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 + } + + 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; + #ifdef 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 + 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_) { + bool log_to_stderr = true; +#ifdef WIN32 + static bool debugger_present = (IsDebuggerPresent() != FALSE); + if (debugger_present) { + log_to_stderr = false; + 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; + ::WriteFile(error_handle, str.data(), str.size(), &written, 0); + } + } +#endif // WIN32 + if (log_to_stderr) { + std::cerr << str; + std::cerr.flush(); + } + } + + if (severity_ >= stream_sev_) { + // If write isn't fully successful, what are we going to do, log it? :) + stream_->WriteAll(str.data(), str.size(), NULL, NULL); + } +} + +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; + min_sev_ = _min(dbg_sev_, stream_sev_); +} + +void LogMessage::LogToStream(StreamInterface* stream, int min_sev) { + delete stream_; + stream_ = stream; + stream_sev_ = (stream_ == 0) ? NO_LOGGING : min_sev; + min_sev_ = _min(dbg_sev_, stream_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; +} + +////////////////////////////////////////////////////////////////////// +// Logging Helpers +////////////////////////////////////////////////////////////////////// + +void LogMultiline(LoggingSeverity level, const char* label, bool input, + const char * data, size_t len, bool hex_mode, + LogMultilineState* state) { + if (!LOG_CHECK_LEVEL_V(level)) + return; + + const char * direction = (input ? " << " : " >> "); + 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 = static_cast<unsigned char>(data[i]); + asc_line[i] = isprint(ch) ? data[i] : '.'; + 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 << " "; + data += line_len; + len -= line_len; + } + return; + } + + size_t consecutive_unprintable = state ? state->unprintable_count_ : 0; + + std::string str(data, len); + while (!str.empty()) { + size_t line_end_length = 0; + std::string::size_type pos = str.find('\n'); + std::string substr = str; + if (pos == std::string::npos) { + substr = str; + str.clear(); + } else if ((pos > 0) && (str[pos-1] == '\r')) { + line_end_length = 2; + substr = str.substr(0, pos - 1); + str = str.substr(pos + 1); + } else { + line_end_length = 1; + substr = str.substr(0, pos); + str = str.substr(pos + 1); + } + + // Any lines which consist entirely of ascii characters are printed. Other + // lines are considered binary, and we just count the number of bytes. + // This algorithm should be very compatible with HTTP transfers of binary + // data. + bool is_ascii = true, is_whitespace = true; + for (size_t i=0; i<substr.size(); ++i) { + unsigned char ch = static_cast<unsigned char>(substr[i]); + if (!isprint(ch)) { + is_ascii = false; + break; + } else if (!isspace(static_cast<unsigned char>(ch))) { + is_whitespace = false; + } + } + // Treat an empty line following binary data as binary. + if (is_whitespace && consecutive_unprintable) { + is_ascii = false; + } + if (!is_ascii) { + consecutive_unprintable += substr.size() + line_end_length; + } + if (consecutive_unprintable && (is_ascii || str.empty())) { + LOG_V(level) << label << direction << "## " << consecutive_unprintable + << " consecutive unprintable ##"; + } + if (is_ascii) { + consecutive_unprintable = 0; + } else { + continue; + } + + // Filter out any private data + 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_ = consecutive_unprintable; + } +} + +////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/logging.h b/third_party/libjingle/files/talk/base/logging.h new file mode 100644 index 0000000..a4686b7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/logging.h @@ -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. + */ + +// 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 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. + +#ifndef TALK_BASE_LOGGING_H__ +#define TALK_BASE_LOGGING_H__ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sstream> +#include "talk/base/basictypes.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. +// ERRCTX_ERRNO: the value was read from global 'errno' +// ERRCTX_HRESULT: the value is a Windows HRESULT +enum LogErrorContext { ERRCTX_NONE, ERRCTX_ERRNO, ERRCTX_HRESULT }; + +class LogMessage { + public: + 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_; } + + enum { NO_LOGGING = LS_ERROR + 1 }; + + // 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. + static void LogToStream(StreamInterface* stream, int min_sev); + static int GetLogToStream() { return stream_sev_; } + + // 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_; } + + private: + // These assist in formatting some parts of the debug output. + static const char* Describe(LoggingSeverity sev); + static const char* DescribeFile(const char* file); + + // 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_; + + // dbg_sev_ and stream_sev_ are 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_, stream_sev_, ctx_sev_; + + // The output stream, if any + static StreamInterface * stream_; + + // 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_; + LogMultilineState() : unprintable_count_(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 char * 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 + +#define LOG(sev) \ + if (talk_base::LogMessage::Loggable(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) \ + if (talk_base::LogMessage::Loggable(sev)) \ + talk_base::LogMessage(__FILE__, __LINE__, sev).stream() + +// The _F version prefixes the message with the current function name. +#define LOG_F(sev) LOG(sev) << __FUNCTION__ << ": " + +// LOG_CHECK_LEVEL can be used as a test before performing expensive or +// sensitive operations whose sole purpose is to output logging data at the +// desired level. +#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); +} + +// PLOG and LOG_ERR attempt to provide a string description of an errno derived +// error. LOG_ERR reads errno directly, so care must be taken to call it before +// errno is reset. +#define PLOG(sev, err) \ + if (talk_base::LogMessage::Loggable(talk_base::sev)) \ + talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \ + talk_base::ERRCTX_ERRNO, err).stream() +#define LOG_ERR(sev) \ + if (talk_base::LogMessage::Loggable(sev)) \ + talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \ + talk_base::ERRCTX_ERRNO, errno).stream() + +// LOG_GLE(M) attempt to provide a string description of the HRESULT returned +// by GetLastError. The second variant allows searching of a dll's string +// table for the error description. +#ifdef WIN32 +#define LOG_GLE(sev) \ + if (talk_base::LogMessage::Loggable(talk_base::sev)) \ + talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \ + talk_base::ERRCTX_HRESULT, GetLastError()).stream() +#define LOG_GLEM(sev, mod) \ + if (talk_base::LogMessage::Loggable(talk_base::sev)) \ + talk_base::LogMessage(__FILE__, __LINE__, talk_base::sev, \ + talk_base::ERRCTX_HRESULT, GetLastError(), mod) \ + .stream() +#endif // WIN32 + +// TODO: Add an "assert" wrapper that logs in the same manner. + +#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 PLOG(sev, err) \ + while (false) talk_base::LogMessage(NULL, 0, talk_base::sev, \ + talk_base::ERRCTX_ERRNO, 0).stream() +#define LOG_ERR(sev) \ + while (false) talk_base::LogMessage(NULL, 0, talk_base::sev, \ + talk_base::ERRCTX_ERRNO, 0).stream() +#ifdef WIN32 +#define LOG_GLE(sev) \ + while (false) talk_base::LogMessage(NULL, 0, talk_base::sev, \ + talk_base::ERRCTX_HRESULT, 0).stream() +#define LOG_GLEM(sev, mod) \ + while (false) talk_base::LogMessage(NULL, 0, talk_base::sev, \ + talk_base::ERRCTX_HRESULT, 0).stream() +#endif // WIN32 + +#endif // !LOGGING +#endif // LOG + +////////////////////////////////////////////////////////////////////// + +} // talk_base + +#endif // TALK_BASE_LOGGING_H__ diff --git a/third_party/libjingle/files/talk/base/md5.h b/third_party/libjingle/files/talk/base/md5.h new file mode 100644 index 0000000..ec458d1 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/md5c.c b/third_party/libjingle/files/talk/base/md5c.c new file mode 100644 index 0000000..eb2c034 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/messagequeue.cc b/third_party/libjingle/files/talk/base/messagequeue.cc new file mode 100644 index 0000000..48a372b --- /dev/null +++ b/third_party/libjingle/files/talk/base/messagequeue.cc @@ -0,0 +1,355 @@ +/* + * 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 +extern "C" { +#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. + ASSERT(!crit_.CurrentThreadIsOwner()); + CritScope cs(&crit_); + message_queues_.push_back(message_queue); +} + +void MessageQueueManager::Remove(MessageQueue *message_queue) { + ASSERT(!crit_.CurrentThreadIsOwner()); // See note above. + 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); +} + +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), new_ss(false), fStop_(false), fPeekKeep_(false), active_(false) { + if (!ss_) { + default_ss_.reset(new PhysicalSocketServer()); + ss_ = default_ss_.get(); + } +} + +MessageQueue::~MessageQueue() { + if (active_) { + MessageQueueManager::Instance()->Remove(this); + Clear(NULL); + } +} + +void MessageQueue::set_socketserver(SocketServer* ss) { + ss_ = ss; +} + +void MessageQueue::Stop() { + fStop_ = true; + ss_->WakeUp(); +} + +bool MessageQueue::IsStopping() { + 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) { + // 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 (msCurrent < dmsgq_.top().msTrigger_) { + cmsDelayNext = dmsgq_.top().msTrigger_ - msCurrent; + break; + } + msgq_.push(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(); + 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 = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + if ((cmsDelayNext != kForever) && (cmsDelayNext < cmsNext)) + cmsNext = cmsDelayNext; + } + + // Wait and multiplex in the meantime + ss_->Wait(cmsNext, true); + + // If the specified timeout expired, return + + msCurrent = Time(); + cmsElapsed = 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(msg); + ss_->WakeUp(); +} + +void MessageQueue::PostDelayed(int cmsDelay, 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; + dmsgq_.push(DelayedMessage(cmsDelay, &msg)); + ss_->WakeUp(); +} + +int MessageQueue::GetDelay() { + CritScope cs(&crit_); + + if (!msgq_.empty()) + return 0; + + if (!dmsgq_.empty()) { + int delay = dmsgq_.top().msTrigger_ - Time(); + if (delay < 0) + delay = 0; + return delay; + } + + return kForever; +} + +void MessageQueue::Clear(MessageHandler *phandler, uint32 id) { + CritScope cs(&crit_); + + // Remove messages with phandler + + if (fPeekKeep_) { + if (phandler == NULL || msgPeek_.phandler == phandler) { + if (id == MQID_ANY || msgPeek_.message_id == id) { + delete msgPeek_.pdata; + fPeekKeep_ = false; + } + } + } + + // Remove from ordered message queue + + size_t c = msgq_.size(); + while (c-- != 0) { + Message msg = msgq_.front(); + msgq_.pop(); + if (phandler != NULL && msg.phandler != phandler) { + msgq_.push(msg); + } else { + if (id == MQID_ANY || msg.message_id == id) { + delete msg.pdata; + } else { + msgq_.push(msg); + } + } + } + + // Remove from priority queue. Not directly iterable, so use this approach + + std::queue<DelayedMessage> dmsgs; + while (!dmsgq_.empty()) { + DelayedMessage dmsg = dmsgq_.top(); + dmsgq_.pop(); + if (phandler != NULL && dmsg.msg_.phandler != phandler) { + dmsgs.push(dmsg); + } else { + if (id == MQID_ANY || dmsg.msg_.message_id == id) { + delete dmsg.msg_.pdata; + } else { + dmsgs.push(dmsg); + } + } + } + while (!dmsgs.empty()) { + dmsgq_.push(dmsgs.front()); + dmsgs.pop(); + } +} + +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/third_party/libjingle/files/talk/base/messagequeue.h b/third_party/libjingle/files/talk/base/messagequeue.h new file mode 100644 index 0000000..5ef976d --- /dev/null +++ b/third_party/libjingle/files/talk/base/messagequeue.h @@ -0,0 +1,210 @@ +/* + * 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 <vector> +#include <queue> +#include <algorithm> +#include "talk/base/basictypes.h" +#include "talk/base/criticalsection.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/socketserver.h" +#include "talk/base/time.h" + +namespace talk_base { + +struct Message; +class MessageQueue; +class MessageHandler; + +// 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_; +}; + +// Messages get dispatched to a MessageHandler + +class MessageHandler { +public: + virtual ~MessageHandler() { + MessageQueueManager::Instance()->Clear(this); + } + + virtual void OnMessage(Message *pmsg) = 0; +}; + +// 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: + TypedMessageData(const T& data) : data_(data) { } + const T& data() const { return data_; } + T& data() { return data_; } +private: + 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: + 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)); + } + MessageHandler *phandler; + uint32 message_id; + MessageData *pdata; + uint32 ts_sensitive; +}; + +// DelayedMessage goes into a priority queue, sorted by trigger time + +class DelayedMessage { +public: + DelayedMessage(int cmsDelay, Message *pmsg) { + cmsDelay_ = cmsDelay; + msTrigger_ = GetMillisecondCount() + cmsDelay; + msg_ = *pmsg; + } + + bool operator< (const DelayedMessage& dmsg) const { + return dmsg.msTrigger_ < msTrigger_; + } + + int cmsDelay_; // for debugging + uint32 msTrigger_; + Message msg_; +}; + +class MessageQueue { +public: + MessageQueue(SocketServer* ss = 0); + 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 Stop(); + virtual bool IsStopping(); + 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); + 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); + virtual void Clear(MessageHandler *phandler, uint32 id = MQID_ANY); + virtual void Dispatch(Message *pmsg); + virtual void ReceiveSends(); + virtual int GetDelay(); + + // 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 talk_base::DisposeData<T>(doomed)); + } + } + +protected: + void EnsureActive(); + + SocketServer* ss_; + // If a server isn't supplied in the constructor, use this one. + scoped_ptr<SocketServer> default_ss_; + bool new_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_; + std::queue<Message> msgq_; + std::priority_queue<DelayedMessage> dmsgq_; + CriticalSection crit_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_MESSAGEQUEUE_H__ diff --git a/third_party/libjingle/files/talk/base/nat_unittest.cc b/third_party/libjingle/files/talk/base/nat_unittest.cc new file mode 100644 index 0000000..23c122b --- /dev/null +++ b/third_party/libjingle/files/talk/base/nat_unittest.cc @@ -0,0 +1,223 @@ +#include <string> +#include <cstring> +#include <iostream> +#include <cassert> + +#include "talk/base/natserver.h" +#include "talk/base/testclient.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/virtualsocketserver.h" +#include "talk/base/natsocketfactory.h" +#include "talk/base/host.h" + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +using namespace talk_base; + +#define CHECK(arg) Check(arg, #arg) + +void Check(int result, const char* desc) { + if (result < 0) { + std::cerr << desc << ": " << std::strerror(errno) << std::endl; + exit(1); + } +} + +void CheckTest(bool act_val, bool exp_val, std::string desc) { + if (act_val && !exp_val) { + std::cerr << "error: " << desc << " was true, expected false" << std::endl; + exit(1); + } else if (!act_val && exp_val) { + std::cerr << "error: " << desc << " was false, expected true" << std::endl; + exit(1); + } +} + +void CheckReceive( + TestClient* client, bool should_receive, const char* buf, size_t size) { + if (should_receive) + client->CheckNextPacket(buf, size, 0); + else + client->CheckNoPacket(); +} + +TestClient* CreateTestClient( + SocketFactory* factory, const SocketAddress& local_addr) { + AsyncUDPSocket* socket = CreateAsyncUDPSocket(factory); + CHECK(socket->Bind(local_addr)); + return new TestClient(socket); +} + +void TestNATPorts( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool exp_same) { + + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(NAT_SERVER_PORT); + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, server_addr); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, external_addrs[0]); + SocketAddress trans_addr; + out[0]->CheckNextPacket(buf, len, &trans_addr); + + for (int i = 1; i < 4; i++) { + in->SendTo(buf, len, external_addrs[i]); + SocketAddress trans_addr2; + out[i]->CheckNextPacket(buf, len, &trans_addr2); + bool are_same = (trans_addr == trans_addr2); + CheckTest(are_same, exp_same, "same translated address"); + } + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +void TestPorts( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestNATPorts(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, true); + TestNATPorts(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true); + TestNATPorts(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true); + TestNATPorts(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, false); +} + +void TestNATFilters( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4], + NATType nat_type, bool filter_ip, bool filter_port) { + + Thread th_int(internal); + Thread th_ext(external); + + SocketAddress server_addr = internal_addr; + server_addr.SetPort(NAT_SERVER_PORT); + NATServer* nat = new NATServer( + nat_type, internal, server_addr, external, external_addrs[0]); + NATSocketFactory* natsf = new NATSocketFactory(internal, server_addr); + + TestClient* in = CreateTestClient(natsf, internal_addr); + TestClient* out[4]; + for (int i = 0; i < 4; i++) + out[i] = CreateTestClient(external, external_addrs[i]); + + th_int.Start(); + th_ext.Start(); + + const char* buf = "filter_test"; + size_t len = strlen(buf); + + in->SendTo(buf, len, external_addrs[0]); + SocketAddress trans_addr; + out[0]->CheckNextPacket(buf, len, &trans_addr); + + out[1]->SendTo(buf, len, trans_addr); + CheckReceive(in, !filter_ip, buf, len); + + out[2]->SendTo(buf, len, trans_addr); + CheckReceive(in, !filter_port, buf, len); + + out[3]->SendTo(buf, len, trans_addr); + CheckReceive(in, !filter_ip && !filter_port, buf, len); + + th_int.Stop(); + th_ext.Stop(); + + delete nat; + delete natsf; + delete in; + for (int i = 0; i < 4; i++) + delete out[i]; +} + +void TestFilters( + SocketServer* internal, const SocketAddress& internal_addr, + SocketServer* external, const SocketAddress external_addrs[4]) { + TestNATFilters(internal, internal_addr, external, external_addrs, + NAT_OPEN_CONE, false, false); + TestNATFilters(internal, internal_addr, external, external_addrs, + NAT_ADDR_RESTRICTED, true, false); + TestNATFilters(internal, internal_addr, external, external_addrs, + NAT_PORT_RESTRICTED, true, true); + TestNATFilters(internal, internal_addr, external, external_addrs, + NAT_SYMMETRIC, true, true); +} + +const int PORT0 = 7405; +const int PORT1 = 7450; +const int PORT2 = 7505; + +int main(int argc, char* argv[]) { + assert(LocalHost().networks().size() >= 2); + SocketAddress int_addr(LocalHost().networks()[1]->ip(), PORT0); + + std::string ext_ip1 = + SocketAddress::IPToString(LocalHost().networks()[0]->ip()); // 127.0.0.1 + std::string ext_ip2 = + SocketAddress::IPToString(LocalHost().networks()[1]->ip()); // 127.0.0.2 + assert(int_addr.IPAsString() != ext_ip1); + //assert(int_addr.IPAsString() != ext_ip2); // uncomment + + SocketAddress ext_addrs[4] = { + SocketAddress(ext_ip1, PORT1), + SocketAddress(ext_ip2, PORT1), + SocketAddress(ext_ip1, PORT2), + SocketAddress(ext_ip2, PORT2) + }; + + PhysicalSocketServer* int_pss = new PhysicalSocketServer(); + PhysicalSocketServer* ext_pss = new PhysicalSocketServer(); + + std::cout << "Testing on physical network:" << std::endl; + TestPorts(int_pss, int_addr, ext_pss, ext_addrs); + std::cout << "ports: PASS" << std::endl; + TestFilters(int_pss, int_addr, ext_pss, ext_addrs); + std::cout << "filters: PASS" << std::endl; + + VirtualSocketServer* int_vss = new VirtualSocketServer(); + VirtualSocketServer* ext_vss = new VirtualSocketServer(); + + int_addr.SetIP(int_vss->GetNextIP()); + ext_addrs[0].SetIP(ext_vss->GetNextIP()); + ext_addrs[1].SetIP(ext_vss->GetNextIP()); + ext_addrs[2].SetIP(ext_addrs[0].ip()); + ext_addrs[3].SetIP(ext_addrs[1].ip()); + + std::cout << "Testing on virtual network:" << std::endl; + TestPorts(int_vss, int_addr, ext_vss, ext_addrs); + std::cout << "ports: PASS" << std::endl; + TestFilters(int_vss, int_addr, ext_vss, ext_addrs); + std::cout << "filters: PASS" << std::endl; + + return 0; +} diff --git a/third_party/libjingle/files/talk/base/natserver.cc b/third_party/libjingle/files/talk/base/natserver.cc new file mode 100644 index 0000000..336cf64 --- /dev/null +++ b/third_party/libjingle/files/talk/base/natserver.cc @@ -0,0 +1,210 @@ +/* + * 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 <cassert> +#include <iostream> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +#include "talk/base/natserver.h" + +namespace talk_base { + +RouteCmp::RouteCmp(NAT* nat) : symmetric(nat->IsSymmetric()) { +} + +size_t RouteCmp::operator()(const SocketAddressPair& r) const { + size_t h = r.source().Hash(); + if (symmetric) + h ^= r.destination().Hash(); + return h; +} + +bool RouteCmp::operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const { + if (r1.source() < r2.source()) + return true; + if (r2.source() < r1.source()) + return false; + if (symmetric && (r1.destination() < r2.destination())) + return true; + if (symmetric && (r2.destination() < r1.destination())) + return false; + return false; +} + +AddrCmp::AddrCmp(NAT* nat) + : use_ip(nat->FiltersIP()), use_port(nat->FiltersPort()) { +} + +size_t AddrCmp::operator()(const SocketAddress& a) const { + size_t h = 0; + if (use_ip) + h ^= a.ip(); + if (use_port) + h ^= a.port() | (a.port() << 16); + return h; +} + +bool AddrCmp::operator()( + const SocketAddress& a1, const SocketAddress& a2) const { + if (use_ip && (a1.ip() < a2.ip())) + return true; + if (use_ip && (a2.ip() < a1.ip())) + return false; + if (use_port && (a1.port() < a2.port())) + return true; + if (use_port && (a2.port() < a1.port())) + return false; + return false; +} + +NATServer::NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip) + : external_(external), external_ip_(external_ip) { + nat_ = NAT::Create(type); + + server_socket_ = CreateAsyncUDPSocket(internal); + server_socket_->Bind(internal_addr); + server_socket_->SignalReadPacket.connect(this, &NATServer::OnInternalPacket); + + int_map_ = new InternalMap(RouteCmp(nat_)); + ext_map_ = new ExternalMap(); +} + +NATServer::~NATServer() { + for (InternalMap::iterator iter = int_map_->begin(); + iter != int_map_->end(); + iter++) + delete iter->second; + + delete nat_; + delete server_socket_; + delete int_map_; + delete ext_map_; +} + +void NATServer::OnInternalPacket( + const char* buf, size_t size, const SocketAddress& addr, + AsyncPacketSocket* socket) { + + // Read the intended destination from the wire. + SocketAddress dest_addr; + dest_addr.Read_(buf, size); + + // Find the translation for these addresses (allocating one if necessary). + SocketAddressPair route(addr, dest_addr); + InternalMap::iterator iter = int_map_->find(route); + if (iter == int_map_->end()) { + Translate(route); + iter = int_map_->find(route); + } + assert(iter != int_map_->end()); + + // Allow the destination to send packets back to the source. + iter->second->whitelist->insert(dest_addr); + + // Send the packet to its intended destination. + iter->second->socket->SendTo( + buf + dest_addr.Size_(), size - dest_addr.Size_(), dest_addr); +} + +void NATServer::OnExternalPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + + SocketAddress local_addr = socket->GetLocalAddress(); + + // Find the translation for this addresses. + ExternalMap::iterator iter = ext_map_->find(local_addr); + assert(iter != ext_map_->end()); + + // Allow the NAT to reject this packet. + if (Filter(iter->second, remote_addr)) { + std::cerr << "Packet from " << remote_addr.ToString() + << " was filtered out by the NAT." << std::endl; + return; + } + + // Forward this packet to the internal address. + + size_t real_size = size + remote_addr.Size_(); + char* real_buf = new char[real_size]; + + remote_addr.Write_(real_buf, real_size); + std::memcpy(real_buf + remote_addr.Size_(), buf, size); + + server_socket_->SendTo(real_buf, real_size, iter->second->route.source()); + + delete[] real_buf; +} + +void NATServer::Translate(const SocketAddressPair& route) { + AsyncUDPSocket* socket = CreateAsyncUDPSocket(external_); + + SocketAddress ext_addr = external_ip_; + for (int i = 0; i < 65536; i++) { + ext_addr.SetPort((route.source().port() + i) % 65536); + if (ext_map_->find(ext_addr) == ext_map_->end()) { + int result = socket->Bind(ext_addr); + if ((result < 0) && (socket->GetError() == EADDRINUSE)) + continue; + assert(result >= 0); // TODO: do something better + + TransEntry* entry = new TransEntry(route, socket, nat_); + (*int_map_)[route] = entry; + (*ext_map_)[ext_addr] = entry; + socket->SignalReadPacket.connect(this, &NATServer::OnExternalPacket); + return; + } + } + + std::cerr << "Couldn't find a free port!" << std::endl; + delete socket; + exit(1); +} + +bool NATServer::Filter(TransEntry* entry, const SocketAddress& ext_addr) { + return entry->whitelist->find(ext_addr) == entry->whitelist->end(); +} + +NATServer::TransEntry::TransEntry( + const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat) + : route(r), socket(s) { + whitelist = new AddressSet(AddrCmp(nat)); +} + +NATServer::TransEntry::~TransEntry() { + delete socket; +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/natserver.h b/third_party/libjingle/files/talk/base/natserver.h new file mode 100644 index 0000000..61c1dd3 --- /dev/null +++ b/third_party/libjingle/files/talk/base/natserver.h @@ -0,0 +1,114 @@ +/* + * 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_NATSERVER_H__ +#define TALK_BASE_NATSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/socketaddresspair.h" +#include "talk/base/thread.h" +#include "talk/base/socketfactory.h" +#include "talk/base/nattypes.h" +#include <map> + +namespace talk_base { + +// Change how routes (socketaddress pairs) are compared based on the type of +// NAT. The NAT server maintains a hashtable of the routes that it knows +// about. So these affect which routes are treated the same. +struct RouteCmp { + RouteCmp(NAT* nat); + size_t operator()(const SocketAddressPair& r) const; + bool operator()( + const SocketAddressPair& r1, const SocketAddressPair& r2) const; + + bool symmetric; +}; + +// Changes how addresses are compared based on the filtering rules of the NAT. +struct AddrCmp { + AddrCmp(NAT* nat); + size_t operator()(const SocketAddress& r) const; + bool operator()(const SocketAddress& r1, const SocketAddress& r2) const; + + bool use_ip; + bool use_port; +}; + +// Implements the NAT device. It listens for packets on the internal network, +// translates them, and sends them out over the external network. + +const int NAT_SERVER_PORT = 4237; + +class NATServer : public sigslot::has_slots<> { +public: + NATServer( + NATType type, SocketFactory* internal, const SocketAddress& internal_addr, + SocketFactory* external, const SocketAddress& external_ip); + ~NATServer(); + + // Packets received on one of the networks. + void OnInternalPacket( + const char* buf, size_t size, const SocketAddress& addr, + AsyncPacketSocket* socket); + void OnExternalPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); + +private: + typedef std::set<SocketAddress,AddrCmp> AddressSet; + + /* Records a translation and the associated external socket. */ + struct TransEntry { + TransEntry(const SocketAddressPair& r, AsyncUDPSocket* s, NAT* nat); + ~TransEntry(); + + SocketAddressPair route; + AsyncUDPSocket* socket; + AddressSet* whitelist; + }; + + typedef std::map<SocketAddressPair,TransEntry*,RouteCmp> InternalMap; + typedef std::map<SocketAddress,TransEntry*> ExternalMap; + + NAT* nat_; + AsyncUDPSocket* server_socket_; + SocketFactory* external_; + SocketAddress external_ip_; + InternalMap* int_map_; + ExternalMap* ext_map_; + + /* Creates a new entry that translates the given route. */ + void Translate(const SocketAddressPair& route); + + /* Determines whether the NAT would filter out a packet from this address. */ + bool Filter(TransEntry* entry, const SocketAddress& ext_addr); +}; + +} // namespace talk_base + +#endif // TALK_BASE_NATSERVER_H__ diff --git a/third_party/libjingle/files/talk/base/natserver_main.cc b/third_party/libjingle/files/talk/base/natserver_main.cc new file mode 100644 index 0000000..a748108 --- /dev/null +++ b/third_party/libjingle/files/talk/base/natserver_main.cc @@ -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. + */ + +#include <iostream> + +#include "talk/base/natserver.h" +#include "talk/base/host.h" +#include "talk/base/physicalsocketserver.h" + +using namespace talk_base; + +int main(int argc, char* argv[]) { + if (argc != 3) { + std::cerr << "usage: natserver <internal-ip> <external-ip>" << std::endl; + exit(1); + } + + SocketAddress internal = SocketAddress(argv[1]); + SocketAddress external = SocketAddress(argv[2]); + if (internal.EqualIPs(external)) { + std::cerr << "internal and external IPs must differ" << std::endl; + exit(1); + } + + Thread* pthMain = Thread::Current(); + PhysicalSocketServer* ss = new PhysicalSocketServer(); + pthMain->set_socketserver(ss); + NATServer* server = new NATServer(NAT_OPEN_CONE, ss, internal, ss, external); + server = server; + + pthMain->Run(); + return 0; +} diff --git a/third_party/libjingle/files/talk/base/natsocketfactory.cc b/third_party/libjingle/files/talk/base/natsocketfactory.cc new file mode 100644 index 0000000..2b0309e --- /dev/null +++ b/third_party/libjingle/files/talk/base/natsocketfactory.cc @@ -0,0 +1,228 @@ +/* + * 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 <cassert> +#include "talk/base/natsocketfactory.h" + +namespace talk_base { + +class NATSocket : public AsyncSocket { +public: + NATSocket(Socket* socket, const SocketAddress& server_addr) + : async_(false), connected_(false), server_addr_(server_addr), + socket_(socket), buf_(0), size_(0) { + } + + NATSocket(AsyncSocket* socket, const SocketAddress& server_addr) + : async_(true), connected_(false), server_addr_(server_addr), + socket_(socket), buf_(0), size_(0) { + socket->SignalReadEvent.connect(this, &NATSocket::OnReadEvent); + socket->SignalWriteEvent.connect(this, &NATSocket::OnWriteEvent); + } + + virtual ~NATSocket() { + delete socket_; + delete buf_; + } + + SocketAddress GetLocalAddress() const { + return socket_->GetLocalAddress(); + } + + SocketAddress GetRemoteAddress() const { + return remote_addr_; // will be ANY if not connected + } + + int Bind(const SocketAddress& addr) { + return socket_->Bind(addr); + } + + int Connect(const SocketAddress& addr) { + connected_ = true; + remote_addr_ = addr; + return 0; + } + + int Send(const void *pv, size_t cb) { + assert(connected_); + return SendInternal(pv, cb, remote_addr_); + } + + int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + assert(!connected_); + return SendInternal(pv, cb, addr); + } + + int SendInternal(const void *pv, size_t cb, const SocketAddress& addr) { + size_t size = cb + addr.Size_(); + char* buf = new char[size]; + Encode(static_cast<const char*>(pv), cb, buf, size, addr); + + int result = socket_->SendTo(buf, size, server_addr_); + delete buf; + if (result < 0) { + return result; + } else { + assert(result == static_cast<int>(size)); // TODO: This isn't fair. + return (int)((size_t)result - addr.Size_()); + } + } + + int Recv(void *pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + + int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + // Make sure we have enough room to read the requested amount plus the + // header address. + SocketAddress remote_addr; + Grow(cb + remote_addr.Size_()); + + // Read the packet from the socket. + int result = socket_->RecvFrom(buf_, size_, &remote_addr); + if (result < 0) + return result; + assert(remote_addr == server_addr_); + + // TODO: we need better framing so that we know how many bytes we can + // return before we need to read the next address. For UDP, this will be + // fine as long as the reader always reads everything in the packet. + assert((size_t)result < size_); + + // Decode the wire packet into the actual results. + SocketAddress real_remote_addr; + size_t real_size = cb; + Decode(buf_, result, pv, &real_size, &real_remote_addr); + + // Make sure this packet should be delivered before returning it. + if (!connected_ || (real_remote_addr == remote_addr_)) { + if (paddr) + *paddr = real_remote_addr; + return (int)real_size; + } else { + std::cerr << "Dropping packet from unknown remote address: " + << real_remote_addr.ToString() << std::endl; + return 0; // Tell the caller we didn't read anything + } + } + + int Close() { + connected_ = false; + remote_addr_ = SocketAddress(); + return socket_->Close(); + } + + int Listen(int backlog) { + assert(false); // not yet implemented + return 0; + } + + Socket* Accept(SocketAddress *paddr) { + assert(false); // not yet implemented + return 0; + } + + AsyncSocket* asyncsocket() { + assert(async_); + return static_cast<AsyncSocket*>(socket_); + } + + int GetError() const { return socket_->GetError(); } + void SetError(int error) { socket_->SetError(error); } + + ConnState GetState() const { return connected_ ? CS_CONNECTED : CS_CLOSED; } + + virtual int EstimateMTU(uint16* mtu) { return socket_->EstimateMTU(mtu); } + virtual int SetOption(Option opt, int value) { return socket_->SetOption(opt, value); } + + void OnReadEvent(AsyncSocket* socket) { + assert(socket == socket_); + SignalReadEvent(this); + } + + void OnWriteEvent(AsyncSocket* socket) { + assert(socket == socket_); + SignalWriteEvent(this); + } + +private: + // Makes sure the buffer is at least the given size. + void Grow(size_t new_size) { + if (size_ < new_size) { + delete buf_; + size_ = new_size; + buf_ = new char[size_]; + } + } + + // Encodes the given data and intended remote address into a packet to send + // to the NAT server. + void Encode(const char* data, size_t data_size, char* buf, size_t buf_size, + const SocketAddress& remote_addr) { + assert(buf_size == data_size + remote_addr.Size_()); + remote_addr.Write_(buf, (int)buf_size); + std::memcpy(buf + remote_addr.Size_(), data, data_size); + } + + // Decodes the given packet from the NAT server into the actual remote + // address and data. + void Decode(const char* data, size_t data_size, void* buf, size_t* buf_size, + SocketAddress* remote_addr) { + assert(data_size >= remote_addr->Size_()); + assert(data_size <= *buf_size + remote_addr->Size_()); + remote_addr->Read_(data, (int)data_size); + *buf_size = data_size - remote_addr->Size_(); + std::memcpy(buf, data + remote_addr->Size_(), *buf_size); + } + + bool async_; + bool connected_; + SocketAddress remote_addr_; + SocketAddress server_addr_; // address of the NAT server + Socket* socket_; + char* buf_; + size_t size_; +}; + +NATSocketFactory::NATSocketFactory( + SocketFactory* factory, const SocketAddress& nat_addr) + : factory_(factory), nat_addr_(nat_addr) { +} + +Socket* NATSocketFactory::CreateSocket(int type) { + assert(type == SOCK_DGRAM); // TCP is not yet suported + return new NATSocket(factory_->CreateSocket(type), nat_addr_); +} + +AsyncSocket* NATSocketFactory::CreateAsyncSocket(int type) { + assert(type == SOCK_DGRAM); // TCP is not yet suported + return new NATSocket(factory_->CreateAsyncSocket(type), nat_addr_); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/natsocketfactory.h b/third_party/libjingle/files/talk/base/natsocketfactory.h new file mode 100644 index 0000000..a689158 --- /dev/null +++ b/third_party/libjingle/files/talk/base/natsocketfactory.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_NATSOCKETFACTORY_H__ +#define TALK_BASE_NATSOCKETFACTORY_H__ + +#include "talk/base/socketfactory.h" + +namespace talk_base { + +// Creates sockets that will send all traffic through a NAT. The actual data +// is sent using sockets from a socket factory, given to the constructor. +class NATSocketFactory : public SocketFactory { +public: + NATSocketFactory(SocketFactory* factory, const SocketAddress& nat_addr); + + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + +private: + SocketFactory* factory_; + SocketAddress nat_addr_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_NATSOCKETFACTORY_H__ diff --git a/third_party/libjingle/files/talk/base/nattypes.cc b/third_party/libjingle/files/talk/base/nattypes.cc new file mode 100644 index 0000000..290c3ad --- /dev/null +++ b/third_party/libjingle/files/talk/base/nattypes.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 <cassert> + +#include "talk/base/nattypes.h" + +namespace talk_base { + +class SymmetricNAT : public NAT { +public: + bool IsSymmetric() { return true; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +class OpenConeNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return false; } + bool FiltersPort() { return false; } +}; + +class AddressRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return false; } +}; + +class PortRestrictedNAT : public NAT { +public: + bool IsSymmetric() { return false; } + bool FiltersIP() { return true; } + bool FiltersPort() { return true; } +}; + +NAT* NAT::Create(NATType type) { + switch (type) { + case NAT_OPEN_CONE: return new OpenConeNAT(); + case NAT_ADDR_RESTRICTED: return new AddressRestrictedNAT(); + case NAT_PORT_RESTRICTED: return new PortRestrictedNAT(); + case NAT_SYMMETRIC: return new SymmetricNAT(); + default: assert(0); return 0; + } +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/nattypes.h b/third_party/libjingle/files/talk/base/nattypes.h new file mode 100644 index 0000000..aad11bb --- /dev/null +++ b/third_party/libjingle/files/talk/base/nattypes.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 TALK_BASE_NATTYPE_H__ +#define TALK_BASE_NATTYPE_H__ + +namespace talk_base { + +/* Identifies each type of NAT that can be simulated. */ +enum NATType { + NAT_OPEN_CONE, + NAT_ADDR_RESTRICTED, + NAT_PORT_RESTRICTED, + NAT_SYMMETRIC +}; + +// Implements the rules for each specific type of NAT. +class NAT { +public: + // Determines whether this NAT uses both source and destination address when + // checking whether a mapping already exists. + virtual bool IsSymmetric() = 0; + + // Determines whether this NAT drops packets received from a different IP + // the one last sent to. + virtual bool FiltersIP() = 0; + + // Determines whether this NAT drops packets received from a different port + // the one last sent to. + virtual bool FiltersPort() = 0; + + // Returns an implementation of the given type of NAT. + static NAT* Create(NATType type); +}; + +} // namespace talk_base + +#endif // TALK_BASE_NATTYPE_H__ diff --git a/third_party/libjingle/files/talk/base/network.cc b/third_party/libjingle/files/talk/base/network.cc new file mode 100644 index 0000000..d5a7d24 --- /dev/null +++ b/third_party/libjingle/files/talk/base/network.cc @@ -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. + */ + +#include <algorithm> +#include <cassert> +#include <cfloat> +#include <cmath> +#include <sstream> + +#ifdef POSIX +extern "C" { +#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 "talk/base/host.h" +#include "talk/base/logging.h" +#include "talk/base/network.h" +#include "talk/base/socket.h" // this includes something that makes windows happy +#include "talk/base/stringencode.h" +#include "talk/base/time.h" +#include "talk/base/basicdefs.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"; +} + +#if 0 +const std::string TEST_MAP0_IN = ""; +const std::string TEST_MAP0_OUT = "{}"; +const std::string TEST_MAP1 = "{a=12345}"; +const std::string TEST_MAP2 = "{a=12345,b=67890}"; +const std::string TEST_MAP3 = "{a=12345,b=67890,c=13579}"; +const std::string TEST_MAP4 = "{a={d=12345,e=67890}}"; +const std::string TEST_MAP5 = "{a={d=12345,e=67890},b=67890}"; +const std::string TEST_MAP6 = "{a=12345,b={d=12345,e=67890}}"; +const std::string TEST_MAP7 = "{a=12345,b={d=12345,e=67890},c=13579}"; + +class MyTest { +public: + MyTest() { + test(TEST_MAP0_IN, TEST_MAP0_OUT); + test(TEST_MAP1, TEST_MAP1); + test(TEST_MAP2, TEST_MAP2); + test(TEST_MAP3, TEST_MAP3); + test(TEST_MAP4, TEST_MAP4); + test(TEST_MAP5, TEST_MAP5); + test(TEST_MAP6, TEST_MAP6); + test(TEST_MAP7, TEST_MAP7); + } + void test(const std::string& input, const std::string& exp_output) { + StrMap map; + ParseMap(input, map); + std::string output; + BuildMap(map, output); + LOG(INFO) << " ******** " << (output == exp_output); + } +}; + +static MyTest myTest; +#endif + +} // namespace + +namespace talk_base { + +#ifdef POSIX +void NetworkManager::CreateNetworks(std::vector<Network*>& networks) { + int fd; + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { + PLOG(LERROR, errno) << "socket"; + return; + } + + struct ifconf ifc; + ifc.ifc_len = 64 * sizeof(struct ifreq); + ifc.ifc_buf = new char[ifc.ifc_len]; + + if (ioctl(fd, SIOCGIFCONF, &ifc) < 0) { + PLOG(LERROR, errno) << "ioctl"; + return; + } + 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) { + if (strcmp(ptr->ifr_name, "lo")) { // Ignore the loopback device + 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); + networks.push_back(new Network(std::string(ptr->ifr_name), ip)); + } + } +#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); +} +#endif + +#ifdef WIN32 +void NetworkManager::CreateNetworks(std::vector<Network*>& networks) { + IP_ADAPTER_INFO info_temp; + ULONG len = 0; + + if (GetAdaptersInfo(&info_temp, &len) != ERROR_BUFFER_OVERFLOW) + return; + IP_ADAPTER_INFO *infos = new IP_ADAPTER_INFO[len]; + if (GetAdaptersInfo(infos, &len) != NO_ERROR) + return; + + int count = 0; + for (IP_ADAPTER_INFO *info = infos; info != NULL; info = info->Next) { + if (info->Type == MIB_IF_TYPE_LOOPBACK) + continue; + if (strcmp(info->IpAddressList.IpAddress.String, "0.0.0.0") == 0) + continue; + + // In production, don't transmit the network name because of + // privacy concerns. Transmit a number instead. + + std::string name; +#if defined(PRODUCTION) + std::ostringstream ost; + ost << count; + name = ost.str(); + count++; +#else + name = info->Description; +#endif + + networks.push_back(new Network(name, + SocketAddress::StringToIP(info->IpAddressList.IpAddress.String))); + } + + delete infos; +} +#endif + +void NetworkManager::GetNetworks(std::vector<Network*>& result) { + std::vector<Network*> list; + CreateNetworks(list); + + 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()); + delete list[i]; + } + + networks_[network->name()] = network; + result.push_back(network); + } +} + +std::string NetworkManager::GetState() { + StrMap map; + for (NetworkMap::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(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); + network->SetState(state); + networks_[name] = network; + } +} + +Network::Network(const std::string& name, uint32 ip) + : name_(name), ip_(ip), 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 name, plus + // the IP address. + ss << "Net[" << name_.substr(0, name_.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() { + 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(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/third_party/libjingle/files/talk/base/network.h b/third_party/libjingle/files/talk/base/network.h new file mode 100644 index 0000000..52785ba --- /dev/null +++ b/third_party/libjingle/files/talk/base/network.h @@ -0,0 +1,139 @@ +/* + * 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: + + // 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. + void GetNetworks(std::vector<Network*>& networks); + + // Reads and writes the state of the quality database in a string format. + std::string GetState(); + void SetState(std::string str); + + // Creates a network object for each network available on the machine. + static void CreateNetworks(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, uint32 ip); + + // Returns the OS name of this network. This is considered the primary key + // that identifies each network. + const std::string& name() const { return name_; } + + // Identifies the current IP address used by this network. + uint32 ip() const { return ip_; } + void set_ip(uint32 ip) { ip_ = ip; } + + // 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_; + uint32 ip_; + 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(std::string str); + std::string GetState(); + + 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: + // 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/third_party/libjingle/files/talk/base/openssladapter.cc b/third_party/libjingle/files/talk/base/openssladapter.cc new file mode 100644 index 0000000..1d4f02c --- /dev/null +++ b/third_party/libjingle/files/talk/base/openssladapter.cc @@ -0,0 +1,809 @@ +#include <openssl/bio.h> +#include <openssl/ssl.h> +#include <openssl/err.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" + +////////////////////////////////////////////////////////////////////// +// StreamBIO +////////////////////////////////////////////////////////////////////// + +#if 0 +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, +}; + +BIO_METHOD* BIO_s_stream() { return(&methods_stream); } + +BIO* BIO_new_stream(StreamInterface* stream) { + BIO* ret = BIO_new(BIO_s_stream()); + if (ret == NULL) + return NULL; + ret->ptr = stream; + return ret; +} + +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; + } +} +#endif + +////////////////////////////////////////////////////////////////////// +// 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 { + +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) { +} + +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<talk_base::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; + + 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::SSLPostConnectionCheck(SSL* ssl, const char* host) { + 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) { + X509V3_EXT_METHOD* meth = X509V3_EXT_get(extension); + if (!meth) + break; + + void* ext_str = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x0090800fL + const unsigned char **ext_value_data = (const_cast<const unsigned char **> + (&extension->value->data)); +#else + unsigned char **ext_value_data = &extension->value->data; +#endif + + if (meth->it) { + ext_str = ASN1_item_d2i(NULL, ext_value_data, extension->value->length, + ASN1_ITEM_ptr(meth->it)); + } else { + ext_str = meth->d2i(NULL, ext_value_data, 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); + if (!strcmp(nval->name, "DNS") && !strcmp(nval->value, host)) { + ok = true; + break; + } + } + } + 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; + } + + if (ok) + ok = (SSL_get_verify_result(ssl) == X509_V_OK); + + 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 && stream->ignore_bad_cert()) { + LOG(LS_WARNING) << "Ignoring cert error while verifying cert chain"; + ok = 1; + } + + return ok; +} + +SSL_CTX* +OpenSSLAdapter::SetupSSLContext() { + SSL_CTX* ctx = SSL_CTX_new(TLSv1_client_method()); + if (ctx == NULL) + return NULL; + + // Add the root cert to the SSL context +#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) { + SSL_CTX_free(ctx); + return NULL; + } + if (!X509_STORE_add_cert(SSL_CTX_get_cert_store(ctx), cert)) { + X509_free(cert); + 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 diff --git a/third_party/libjingle/files/talk/base/openssladapter.h b/third_party/libjingle/files/talk/base/openssladapter.h new file mode 100644 index 0000000..b794a7b --- /dev/null +++ b/third_party/libjingle/files/talk/base/openssladapter.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_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: + 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(); + + 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 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_; +}; + +///////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_OPENSSLADAPTER_H__ diff --git a/third_party/libjingle/files/talk/base/pathutils.cc b/third_party/libjingle/files/talk/base/pathutils.cc new file mode 100644 index 0000000..4a77879 --- /dev/null +++ b/third_party/libjingle/files/talk/base/pathutils.cc @@ -0,0 +1,426 @@ +/* + * 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/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)); +} + +Pathname::Pathname() + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { +} + +Pathname::Pathname(const std::string& pathname) + : folder_delimiter_(DEFAULT_FOLDER_DELIM) { + SetPathname(pathname); +} + +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(); +} + +std::string Pathname::pathname() const { + std::string pathname(folder_); + pathname.append(basename_); + pathname.append(extension_); + return pathname; +} + +std::string Pathname::url() const { + std::string s = "file://"; + for (size_t i=0; i<folder_.length(); ++i) { + if (i == 1 && folder_[i] == ':') // drive letter + s += '|'; + else if (IsFolderDelimiter(folder_[i])) + s += '/'; + else + s += folder_[i]; + } + s += basename_; + s += extension_; + return UrlEncodeString(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::AppendPathname(const Pathname& pathname) { + std::string full_pathname(folder_); + full_pathname.append(pathname.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_; +} + +void Pathname::SetBasename(const std::string& basename) { + ASSERT(basename.find_first_of(FOLDER_DELIMS) == std::string::npos); + basename_.assign(basename); +} + +std::string Pathname::extension() const { + return extension_; +} + +void Pathname::SetExtension(const std::string& extension) { + ASSERT(extension.find_first_of(FOLDER_DELIMS) == std::string::npos); + ASSERT(extension.find_first_of(EXT_DELIM, 1) == std::string::npos); + extension_.assign(extension); + // Ensure extension begins with the extension delimiter + if (!extension_.empty() && (extension_[0] != EXT_DELIM)) { + extension_.insert(extension_.begin(), EXT_DELIM); + } +} + +std::string Pathname::filename() const { + std::string filename(basename_); + filename.append(extension_); + return filename; +} + +void Pathname::SetFilename(const std::string& filename) { + std::string::size_type pos = filename.rfind(EXT_DELIM); + if ((pos == std::string::npos) || (pos == 0)) { + SetBasename(filename); + SetExtension(EMPTY_STR); + } else { + SetBasename(filename.substr(0, pos)); + SetExtension(filename.substr(pos)); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// CreateUniqueFile +/////////////////////////////////////////////////////////////////////////////// + +std::string g_organization_name; +std::string g_application_name; + +void SetOrganizationName(const std::string& organization) { + g_organization_name = organization; +} + +void SetApplicationName(const std::string& application) { + g_application_name = application; +} + +bool CreateFolder(const talk_base::Pathname& path) { +#ifdef WIN32 + if (!path.filename().empty()) + return false; + + std::wstring pathname16 = ToUtf16(path.pathname()); + if (!pathname16.empty() && (pathname16[0] != '\\')) { + pathname16 = L"\\\\?\\" + pathname16; + } + + DWORD res = ::GetFileAttributes(pathname16.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 (!path.parent_folder().empty()) { + talk_base::Pathname parent(path); + parent.SetFolder(path.parent_folder()); + if (!CreateFolder(parent)) { + return false; + } + } + + return (::CreateDirectory(pathname16.c_str(), NULL) != 0); +#else // !WIN32 + return false; +#endif // !WIN32 +} + +bool FinishPath(talk_base::Pathname& path, bool create, + const std::string& append) { + if (!append.empty()) { + path.AppendFolder(append); + } + if (create && !CreateFolder(path)) + return false; + return true; +} + +bool GetTemporaryFolder(talk_base::Pathname& path, bool create, + const std::string& append) { + ASSERT(!g_application_name.empty()); +#ifdef WIN32 + TCHAR buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strlen(buffer); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + __T("\\")); + } + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(g_application_name).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + __T("\\")); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + path.clear(); + path.SetFolder(ToUtf8(buffer)); + return FinishPath(path, create, append); +#else // !WIN32 + return false; +#endif // !WIN32 +} + +bool GetAppDataFolder(talk_base::Pathname& path, bool create, + const std::string& append) { + ASSERT(!g_organization_name.empty()); + ASSERT(!g_application_name.empty()); +#ifdef WIN32 + TCHAR buffer[MAX_PATH + 1]; + if (!::SHGetSpecialFolderPath(NULL, buffer, CSIDL_LOCAL_APPDATA, TRUE)) + return false; + if (!::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = talk_base::strcatn(buffer, ARRAY_SIZE(buffer), _T("\\")); + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(g_organization_name).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + __T("\\")); + } + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + ToUtf16(g_application_name).c_str()); + if ((len > 0) && (buffer[len-1] != __T('\\'))) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + __T("\\")); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + path.clear(); + path.SetFolder(ToUtf8(buffer)); + return FinishPath(path, create, append); +#else // !WIN32 + return false; +#endif // !WIN32 +} + +bool CleanupTemporaryFolder() { +#ifdef WIN32 + talk_base::Pathname temp_path; + if (!GetTemporaryFolder(temp_path, false, "")) + return false; + + 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)); +#else // !WIN32 + return false; +#endif // !WIN32 +} +#if 0 +bool CreateUnijqueFile(talk_base::Pathname& path, bool create_empty) { +#ifdef WIN32 + // If not folder is supplied, use the temporary folder + if (path.folder().empty()) { + talk_base::Pathname temp_path; + if (!GetTemporaryFolder(temp_path, true, "")) { + + return false; + } + path.SetFolder(temp_path.folder()); + } + printf("path: %s\n", path.pathname()); + // If not filename is supplied, use a temporary name + if (path.filename().empty()) { + TCHAR filename[MAX_PATH]; + std::wstring folder((ToUtf16)(path.folder())); + if (!::GetTempFileName(folder.c_str(), __T("gt"), 0, filename)) + return false; + ASSERT(wcsncmp(folder.c_str(), filename, folder.length()) == 0); + path.SetFilename(ToUtf8(filename + folder.length())); + if (!create_empty) { + VERIFY(::DeleteFile(ToUtf16(path.pathname()).c_str()) != FALSE); + } + 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(); + std::wstring pathname16 = ToUtf16(pathname).c_str(); + + if (pathname16[0] != __T('\\')) + pathname16 = __T("\\\\?\\") + pathname16; + + HANDLE hfile = CreateFile(pathname16.c_str(), GENERIC_WRITE, 0, + NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL); + if (hfile != INVALID_HANDLE_VALUE) { + CloseHandle(hfile); + if (!create_empty) { + VERIFY(::DeleteFile(pathname16.c_str()) != FALSE); + } + return true; + } else { + int err = GetLastError(); + if (err != ERROR_FILE_EXISTS && err != ERROR_ACCESS_DENIED) { + return false; + } + } + + version += 1; + char version_base[MAX_PATH]; + talk_base::sprintfn(version_base, ARRAY_SIZE(version_base), "%s-%u", + basename.c_str(), version); + path.SetBasename(version_base); + } + return false; +#else // !WIN32 + // TODO: Make this better. + path.SetBasename("/tmp/temp-1"); +#endif // !WIN32 +} +#endif +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/pathutils.h b/third_party/libjingle/files/talk/base/pathutils.h new file mode 100644 index 0000000..eb33edf --- /dev/null +++ b/third_party/libjingle/files/talk/base/pathutils.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_BASE_PATHUTILS_H__ +#define TALK_BASE_PATHUTILS_H__ + +#include <string> + +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); + + Pathname(); + Pathname(const std::string& pathname); + + // 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(); + + std::string url() const; + + std::string pathname() const; + void SetPathname(const std::string& pathname); + + // Append pathname to the current folder (if any). Any existing filename + // will be discarded. + void AppendPathname(const Pathname& 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; + void SetBasename(const std::string& basename); + + std::string extension() const; + // SetExtension will prefix a period, if needed. + void SetExtension(const std::string& extension); + + std::string filename() const; + void SetFilename(const std::string& filename); + +private: + + std::string folder_, basename_, extension_; + char folder_delimiter_; +}; + + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_PATHUTILS_H__ diff --git a/third_party/libjingle/files/talk/base/physicalsocketserver.cc b/third_party/libjingle/files/talk/base/physicalsocketserver.cc new file mode 100644 index 0000000..9cdacdfa --- /dev/null +++ b/third_party/libjingle/files/talk/base/physicalsocketserver.cc @@ -0,0 +1,1132 @@ +/* + * 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 +extern "C" { +#include <string.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/time.h> +#include <unistd.h> +} +#endif + +#ifdef WIN32 +#include <winsock2.h> +#include <ws2tcpip.h> +#define _WINSOCKAPI_ +#include <windows.h> +#undef SetPort +#endif + +#include <algorithm> +#include <iostream> + +#include "talk/base/basictypes.h" +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/time.h" +#include "talk/base/winping.h" + +#ifdef __linux +#define IP_MTU 14 // Until this is integrated from linux/in.h to netinet/in.h +#endif // __linux + +#ifdef WIN32 +class WinsockInitializer { +public: + WinsockInitializer() { + WSADATA wsaData; + WORD wVersionRequested = MAKEWORD(1, 0); + err_ = WSAStartup(wVersionRequested, &wsaData); + } + ~WinsockInitializer() { + WSACleanup(); + } + int error() { + return err_; + } +private: + int err_; +}; +WinsockInitializer g_winsockinit; +#endif + +namespace talk_base { + +const int kfRead = 0x0001; +const int kfWrite = 0x0002; +const int kfConnect = 0x0004; +const int kfClose = 0x0008; + +// 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 IP_HEADER_SIZE = 20; +const uint32 ICMP_HEADER_SIZE = 8; + +class PhysicalSocket : public AsyncSocket { +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) { + if (s != INVALID_SOCKET) + enabled_events_ = kfRead | kfWrite; + } + + 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); + UpdateLastError(); + if (type != SOCK_STREAM) + enabled_events_ = kfRead | kfWrite; + return s_ != INVALID_SOCKET; + } + + SocketAddress GetLocalAddress() const { + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(s_, (sockaddr*)&addr, &addrlen); + ASSERT(addrlen == sizeof(addr)); + talk_base::SocketAddress address; + if (result >= 0) { + address.FromSockAddr(addr); + } else { + ASSERT(result >= 0); + } + return address; + } + + SocketAddress GetRemoteAddress() const { + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(s_, (sockaddr*)&addr, &addrlen); + ASSERT(addrlen == sizeof(addr)); + talk_base::SocketAddress address; + if (result >= 0) { + address.FromSockAddr(addr); + } else { + ASSERT(errno == ENOTCONN); + } + return address; + } + + int Bind(const SocketAddress& addr) { + sockaddr_in saddr; + addr.ToSockAddr(&saddr); + int err = ::bind(s_, (sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + 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; + SocketAddress addr2(addr); + if (addr2.IsUnresolved()) { + LOG(INFO) << "Resolving addr in PhysicalSocket::Connect"; + addr2.Resolve(); // TODO: Do this async later? + } + sockaddr_in saddr; + addr2.ToSockAddr(&saddr); + int err = ::connect(s_, (sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast<int>(s_) << "] Connect(" << addr2.ToString() << ") Ret: " << err << " Error: " << error_; + if (err == 0) { + state_ = CS_CONNECTED; + } else if (IsBlockingError(error_)) { + state_ = CS_CONNECTING; + enabled_events_ |= kfConnect; + } + enabled_events_ |= kfRead | kfWrite; + return err; + } + + int GetError() const { + return error_; + } + + void SetError(int error) { + error_ = error; + } + + ConnState GetState() const { + return state_; + } + + int SetOption(Option opt, int value) { + assert(opt == OPT_DONTFRAGMENT); +#ifdef WIN32 + value = (value == 0) ? 0 : 1; + return ::setsockopt( + s_, IPPROTO_IP, IP_DONTFRAGMENT, reinterpret_cast<char*>(&value), + sizeof(value)); +#endif +#ifdef __linux + value = (value == 0) ? IP_PMTUDISC_DONT : IP_PMTUDISC_DO; + return ::setsockopt( + s_, IPPROTO_IP, IP_MTU_DISCOVER, &value, sizeof(value)); +#endif +#ifdef OSX + // This is not possible on OSX. + return -1; +#endif + } + + int Send(const void *pv, size_t cb) { + int sent = ::send(s_, reinterpret_cast<const char *>(pv), (int)cb, 0); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast<int>(s_) << "] Send(" << cb << ") Ret: " << sent << " Error: " << error_; + ASSERT(sent <= static_cast<int>(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + 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, 0, (sockaddr*)&saddr, + sizeof(saddr)); + UpdateLastError(); + ASSERT(sent <= static_cast<int>(cb)); // We have seen minidumps where this may be false + if ((sent < 0) && IsBlockingError(error_)) { + enabled_events_ |= kfWrite; + } + 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. + error_ = EWOULDBLOCK; + return SOCKET_ERROR; + } + UpdateLastError(); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + 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); + if ((received >= 0) || IsBlockingError(error_)) { + enabled_events_ |= kfRead; + } + return received; + } + + int Listen(int backlog) { + int err = ::listen(s_, backlog); + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + enabled_events_ |= kfRead; + + return err; + } + + Socket* 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; + if (paddr != NULL) + paddr->FromSockAddr(saddr); + enabled_events_ |= kfRead | kfWrite; + return ss_->WrapSocket(s); + } + + int Close() { + if (s_ == INVALID_SOCKET) + return 0; + int err = ::closesocket(s_); + UpdateLastError(); + //LOG(INFO) << "SOCK[" << static_cast<int>(s_) << "] Close() Ret: " << err << " Error: " << error_; + s_ = INVALID_SOCKET; + state_ = CS_CLOSED; + enabled_events_ = 0; + return err; + } + + int EstimateMTU(uint16* mtu) { + SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + +#ifdef WIN32 + + 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; + +#endif // WIN32 + +#ifdef __linux + + 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 = uint16(value); + return 0; + +#endif // __linux + + // TODO: OSX support + } + + SocketServer* socketserver() { return ss_; } + +protected: + PhysicalSocketServer* ss_; + SOCKET s_; + uint32 enabled_events_; + int error_; + ConnState state_; + + void UpdateLastError() { +#ifdef WIN32 + error_ = WSAGetLastError(); +#endif +#ifdef POSIX + error_ = errno; +#endif + } +}; + +#ifdef POSIX +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual int GetDescriptor() = 0; +}; + +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_) { + uint8 b = 0; + if (write(afd_[1], &b, sizeof(b)) < 0) + LOG(LERROR) << "write failed"; + fSignaled_ = true; + } + } + + virtual uint32 GetRequestedEvents() { + return kfRead; + } + + 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; + read(afd_[0], &b, sizeof(b)); + fSignaled_ = false; + } + } + + virtual void OnEvent(uint32 ff, int err) { + assert(false); + } + + virtual int GetDescriptor() { + return afd_[0]; + } + +private: + PhysicalSocketServer *ss_; + int afd_[2]; + bool fSignaled_; + CriticalSection crit_; +}; + +class SocketDispatcher : public Dispatcher, public PhysicalSocket { +public: + SocketDispatcher(PhysicalSocketServer *ss) : PhysicalSocket(ss) { + ss_->Add(this); + } + SocketDispatcher(SOCKET s, PhysicalSocketServer *ss) : PhysicalSocket(ss, s) { + ss_->Add(this); + } + + 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 uint32 GetRequestedEvents() { + return enabled_events_; + } + + virtual void OnPreEvent(uint32 ff) { + if ((ff & kfConnect) != 0) + state_ = CS_CONNECTED; + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if ((ff & kfWrite) != 0) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if ((ff & kfConnect) != 0) { + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if ((ff & kfClose) != 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 uint32 GetRequestedEvents() { + return flags_; + } + + virtual void OnPreEvent(uint32 ff) { + } + + virtual void OnEvent(uint32 ff, int err) { + if ((ff & kfRead) != 0) + SignalReadEvent(this); + if ((ff & kfWrite) != 0) + SignalWriteEvent(this); + if ((ff & kfClose) != 0) + SignalCloseEvent(this, err); + } + + virtual bool readable() { + return (flags_ & kfRead) != 0; + } + + virtual void set_readable(bool value) { + flags_ = value ? (flags_ | kfRead) : (flags_ & ~kfRead); + } + + virtual bool writable() { + return (flags_ & kfWrite) != 0; + } + + virtual void set_writable(bool value) { + flags_ = value ? (flags_ | kfWrite) : (flags_ & ~kfWrite); + } + +private: + PhysicalSocketServer* ss_; + int fd_; + int flags_; +}; + +AsyncFile* PhysicalSocketServer::CreateFile(int fd) { + return new FileDispatcher(fd, this); +} + +#endif // POSIX + +#ifdef WIN32 +class Dispatcher { +public: + virtual uint32 GetRequestedEvents() = 0; + virtual void OnPreEvent(uint32 ff) = 0; + virtual void OnEvent(uint32 ff, int err) = 0; + virtual WSAEVENT GetWSAEvent() = 0; + virtual SOCKET GetSocket() = 0; + virtual bool CheckSignalClose() = 0; +}; + +uint32 FlagsToEvents(uint32 events) { + uint32 ffFD = FD_CLOSE | FD_ACCEPT; + if (events & kfRead) + ffFD |= FD_READ; + if (events & kfWrite) + ffFD |= FD_WRITE; + if (events & kfConnect) + ffFD |= FD_CONNECT; + return ffFD; +} + +class EventDispatcher : public Dispatcher { +public: + EventDispatcher(PhysicalSocketServer *ss) : ss_(ss) { + if (hev_ = WSACreateEvent()) { + 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 & kfConnect) != 0) + state_ = CS_CONNECTED; + } + + virtual void OnEvent(uint32 ff, int err) { + int cache_id = id_; + if ((ff & kfRead) != 0) { + enabled_events_ &= ~kfRead; + SignalReadEvent(this); + } + if (((ff & kfWrite) != 0) && (id_ == cache_id)) { + enabled_events_ &= ~kfWrite; + SignalWriteEvent(this); + } + if (((ff & kfConnect) != 0) && (id_ == cache_id)) { + if (ff != kfConnect) + LOG(LS_VERBOSE) << "Signalled with kfConnect: " << ff; + enabled_events_ &= ~kfConnect; + SignalConnectEvent(this); + } + if (((ff & kfClose) != 0) && (id_ == cache_id)) { + //LOG(INFO) << "SOCK[" << static_cast<int>(s_) << "] OnClose() Error: " << err; + 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; + + 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_); +} + +PhysicalSocketServer::~PhysicalSocketServer() { + 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_); + dispatchers_.push_back(pdispatcher); +} + +void PhysicalSocketServer::Remove(Dispatcher *pdispatcher) { + CritScope cs(&crit_); + dispatchers_.erase(std::remove(dispatchers_.begin(), dispatchers_.end(), pdispatcher), dispatchers_.end()); +} + +#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 (unsigned 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 & kfRead) + FD_SET(fd, &fdsRead); + if (ff & (kfWrite | kfConnect)) + 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 + // todo: do something intelligent + if (n < 0) + return false; + + // If timeout, return success + + if (n == 0) + return true; + + // We have signaled descriptors + + { + CritScope cr(&crit_); + for (unsigned i = 0; i < dispatchers_.size(); i++) { + Dispatcher *pdispatcher = dispatchers_[i]; + int fd = pdispatcher->GetDescriptor(); + uint32 ff = 0; + if (FD_ISSET(fd, &fdsRead)) { + FD_CLR(fd, &fdsRead); + ff |= kfRead; + } + if (FD_ISSET(fd, &fdsWrite)) { + FD_CLR(fd, &fdsWrite); + if (pdispatcher->GetRequestedEvents() & kfConnect) { + ff |= kfConnect; + } else { + ff |= kfWrite; + } + } + if (ff != 0) { + pdispatcher->OnPreEvent(ff); + pdispatcher->OnEvent(ff, 0); + } + } + } + + // 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) { + ptvWait->tv_sec = tvStop.tv_sec - tvT.tv_sec; + ptvWait->tv_usec = tvStop.tv_usec - tvT.tv_usec; + if (ptvWait->tv_usec < 0) { + ptvWait->tv_usec += 1000000; + ptvWait->tv_sec -= 1; + } + } + } + } + + return true; +} +#endif // POSIX + +#ifdef WIN32 +bool PhysicalSocketServer::Wait(int cmsWait, bool process_io) +{ + int cmsTotal = cmsWait; + int cmsElapsed = 0; + uint32 msStart = GetMillisecondCount(); + +#if LOGGING + if (last_tick_dispatch_count_ == 0) { + last_tick_tracked_ = msStart; + } +#endif + + WSAEVENT socket_ev = WSACreateEvent(); + + fWait_ = true; + while (fWait_) { + std::vector<WSAEVENT> events; + std::vector<Dispatcher *> event_owners; + + events.push_back(socket_ev); + + { + CritScope cr(&crit_); + for (size_t i = 0; i < dispatchers_.size(); ++i) { + 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); + } + } + } + + // Which is shorter, the delay wait or the asked wait? + + int cmsNext; + if (cmsWait == kForever) { + cmsNext = cmsWait; + } else { + cmsNext = cmsTotal - cmsElapsed; + if (cmsNext < 0) + cmsNext = 0; + } + + // 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) { + uint32 now = GetMillisecondCount(); + LOG(INFO) << "PhysicalSocketServer took " << TimeDiff(now, last_tick_tracked_) << "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(TimeDiff(now, last_tick_tracked_) > 1000); + + last_tick_tracked_ = now; + last_tick_dispatch_count_ = 0; + } +#endif + + // Failed? + // todo: need a better strategy than this! + + if (dw == WSA_WAIT_FAILED) { + int error = WSAGetLastError(); + assert(false); + WSACloseEvent(socket_ev); + return false; + } + + // Timeout? + + if (dw == WSA_WAIT_TIMEOUT) { + WSACloseEvent(socket_ev); + return true; + } + + // 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) { + for (size_t i = 0; i < dispatchers_.size(); ++i) { + 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 |= kfRead; + if (wsaEvents.lNetworkEvents & FD_WRITE) + ff |= kfWrite; + if (wsaEvents.lNetworkEvents & FD_CONNECT) { + if (wsaEvents.iErrorCode[FD_CONNECT_BIT] == 0) { + ff |= kfConnect; + } else { + // TODO: Decide whether we want to signal connect, but with an error code + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CONNECT_BIT]; + } + } + if (wsaEvents.lNetworkEvents & FD_ACCEPT) + ff |= kfRead; + if (wsaEvents.lNetworkEvents & FD_CLOSE) { + ff |= kfClose; + errcode = wsaEvents.iErrorCode[FD_CLOSE_BIT]; + } + if (ff != 0) { + disp->OnPreEvent(ff); + disp->OnEvent(ff, errcode); + } + } + } + } + + // Reset the network event until new activity occurs + WSAResetEvent(socket_ev); + } + + // Break? + + if (!fWait_) + break; + cmsElapsed = GetMillisecondCount() - msStart; + if ((cmsWait != kForever) && (cmsElapsed >= cmsWait)) { + break; + } + } + + // Done + + WSACloseEvent(socket_ev); + return true; +} +#endif // WIN32 + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/physicalsocketserver.h b/third_party/libjingle/files/talk/base/physicalsocketserver.h new file mode 100644 index 0000000..cc2e707 --- /dev/null +++ b/third_party/libjingle/files/talk/base/physicalsocketserver.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_PHYSICALSOCKETSERVER_H__ +#define TALK_BASE_PHYSICALSOCKETSERVER_H__ + +#include <vector> + +#include "talk/base/asyncfile.h" +#include "talk/base/socketserver.h" +#include "talk/base/criticalsection.h" + +#ifdef POSIX +typedef int SOCKET; +#endif // POSIX + +namespace talk_base { + +class Dispatcher; +class Signaler; + +// 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); +#endif + +private: + std::vector<Dispatcher*> dispatchers_; + Signaler* signal_wakeup_; + CriticalSection crit_; + bool fWait_; + uint32 last_tick_tracked_; + int last_tick_dispatch_count_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_PHYSICALSOCKETSERVER_H__ diff --git a/third_party/libjingle/files/talk/base/proxydetect.cc b/third_party/libjingle/files/talk/base/proxydetect.cc new file mode 100644 index 0000000..2b52315 --- /dev/null +++ b/third_party/libjingle/files/talk/base/proxydetect.cc @@ -0,0 +1,827 @@ +// TODO: Abstract this better for cross-platformability + +#ifdef _WINDOWS +#include "talk/base/win32.h" +#include <shlobj.h> +#endif + +#include "talk/base/httpcommon.h" +#include "talk/base/httpcommon-inl.h" +#include "talk/base/proxydetect.h" +#include "talk/base/stringutils.h" +#include "talk/base/basicdefs.h" + +#if _WINDOWS +#define _TRY_FIREFOX 1 +#define _TRY_WINHTTP 1 +#define _TRY_JSPROXY 0 +#define _TRY_WM_FINDPROXY 0 +#define _TRY_IE_LAN_SETTINGS 1 +#endif // _WINDOWS + +#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 + +using namespace talk_base; + +////////////////////////////////////////////////////////////////////// +// Utility Functions +////////////////////////////////////////////////////////////////////// + +#ifdef _WINDOWS +#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 // _WINDOWS + +////////////////////////////////////////////////////////////////////// +// GetProxySettingsForUrl +////////////////////////////////////////////////////////////////////// + +bool WildMatch(const char * target, const char * pattern) { + while (*pattern) { + if (*pattern == '*') { + if (!*++pattern) { + return true; + } + while (*target) { + if ((toupper(*pattern) == toupper(*target)) && WildMatch(target + 1, pattern + 1)) { + return true; + } + ++target; + } + return false; + } else { + if (toupper(*pattern) != toupper(*target)) { + return false; + } + ++target; + ++pattern; + } + } + return !*target; +} + +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.server()); + return !addr.IsUnresolved() && ((addr.ip() & mask) == (ip & mask)); + } + + // .foo.com + if (*item == '.') { + size_t hostlen = url.server().length(); + return (hostlen > len) + && (stricmp(url.server().c_str() + (hostlen - len), item) == 0); + } + + // localhost or www.*.com + if (!WildMatch(url.server().c_str(), item)) + return false; + + return true; +} + +bool ProxyListMatch(const Url<char>& url, const std::string& slist, char sep) { + const size_t BUFSIZE = 256; + char buffer[BUFSIZE]; + const char* clist = slist.c_str(); + while (*clist) { + // Remove leading space + if (isspace(*clist)) { + ++clist; + continue; + } + // Break on separator + size_t len; + const char * start = clist; + if (const char * end = strchr(clist, sep)) { + len = (end - clist); + clist += len + 1; + } else { + len = strlen(clist); + clist += 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[4] = { 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 (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((int)port); + } + } + + return (proxy.type != PROXY_NONE); +} + +#if _WINDOWS +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; +} +#endif + +#if _TRY_FIREFOX + +#define USE_FIREFOX_PROFILES_INI 1 + +bool GetDefaultFirefoxProfile(std::wstring* profile) { + ASSERT(NULL != profile); + + wchar_t path[MAX_PATH]; + if (SHGetFolderPath(0, CSIDL_APPDATA, 0, SHGFP_TYPE_CURRENT, path) != S_OK) + return false; + + std::wstring profile_root(path); + profile_root.append(L"\\Mozilla\\Firefox\\"); + +#if USE_FIREFOX_PROFILES_INI + std::wstring tmp(profile_root); + tmp.append(L"profiles.ini"); + + FILE * fp = _wfopen(tmp.c_str(), L"rb"); + if (!fp) + return false; + + // [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 + + std::wstring candidate; + bool relative = true; + + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), fp)) { + size_t len = strlen(buffer); + while ((len > 0) && isspace(buffer[len-1])) + buffer[--len] = 0; + if (buffer[0] == '[') { + relative = true; + candidate.clear(); + } else if (strnicmp(buffer, "IsRelative=", 11) == 0) { + relative = (buffer[11] != '0'); + } else if (strnicmp(buffer, "Path=", 5) == 0) { + if (relative) { + candidate = profile_root; + } else { + candidate.clear(); + } + candidate.append(ToUtf16(buffer + 5)); + candidate.append(L"\\"); + } else if (strnicmp(buffer, "Default=", 8) == 0) { + if ((buffer[8] != '0') && !candidate.empty()) { + break; + } + } + } + fclose(fp); + if (candidate.empty()) + return false; + *profile = candidate; + +#else // !USE_FIREFOX_PROFILES_INI + std::wstring tmp(profile_root); + tmp.append(L"Profiles\\*.default"); + WIN32_FIND_DATA fdata; + HANDLE hFind = FindFirstFile(tmp.c_str(), &fdata); + if (hFind == INVALID_HANDLE_VALUE) + return false; + + profile->assign(profile_root); + profile->append(L"Profiles\\"); + profile->append(fdata.cFileName); + profile->append(L"\\"); + FindClose(hFind); +#endif // !USE_FIREFOX_PROFILES_INI + + return true; +} + +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_; +}; + +bool ReadFirefoxPrefs(const std::wstring& filename, + const char * prefix, + StringMap& settings) { + FILE * fp = _wfopen(filename.c_str(), L"rb"); + if (!fp) + return false; + + size_t prefix_len = strlen(prefix); + bool overlong_line = false; + + char buffer[1024]; + while (fgets(buffer, sizeof(buffer), fp)) { + size_t len = strlen(buffer); + bool missing_newline = (len > 0) && (buffer[len-1] != '\n'); + + if (missing_newline) { + overlong_line = true; + continue; + } else if (overlong_line) { + LOG_F(LS_INFO) << "Skipping long line"; + overlong_line = false; + continue; + } + + while ((len > 0) && isspace(buffer[len-1])) + buffer[--len] = 0; + + // Skip blank lines + if ((len == 0) || (buffer[0] == '#') + || (strncmp(buffer, "/*", 2) == 0) + || (strncmp(buffer, " *", 2) == 0)) + continue; + + 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 << "]"; + } + } + fclose(fp); + return true; +} +#endif // _TRY_FIREFOX + +#ifdef WIN32 +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) { + LOG_GLEM(LERROR,WINHTTP) << "WinHttpGetProxyForUrl faulted!!"; + } +#else + success = pWHGPFU(hWinHttp, url, options, info); +#endif + + return success; +} +#endif + +bool GetProxySettingsForUrl(const char* agent, const char* url, + ProxyInfo& proxy, + bool long_operation) { + bool success = false; + Url<char> purl(url); + +#if 0 + assert( WildMatch(_T("A.B.C.D"), _T("a.b.c.d"))); + assert( WildMatch(_T("127.0.0.1"), _T("12*.0.*1"))); + assert(!WildMatch(_T("127.0.0.0"), _T("12*.0.*1"))); + assert(!WildMatch(_T("127.0.0.0"), _T("12*.0.*1"))); + assert( WildMatch(_T("127.1.0.21"), _T("12*.0.*1"))); + assert(!WildMatch(_T("127.1.1.21"), _T("12*.0.*1"))); + purl = PUrl(_T("http://a.b.c:500/")); + wchar_t item[256]; + _tcscpy(item, _T("a.b.c")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("a.x.c")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("a.b.*")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("a.x.*")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T(".b.c")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T(".x.c")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("a.b.c:500")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("a.b.c:501")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); + purl = PUrl(_T("http://1.2.3.4/")); + _tcscpy(item, _T("1.2.3.4")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("1.2.3.5")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("1.2.3.5/31")); + assert( ProxyItemMatch(purl, item, _tcslen(item))); + _tcscpy(item, _T("1.2.3.5/32")); + assert(!ProxyItemMatch(purl, item, _tcslen(item))); +#endif + + bool autoconfig = false; + bool use_firefox = false; + std::string autoconfig_url; + +#if _TRY_FIREFOX + use_firefox = IsDefaultBrowserFirefox(); + + if (use_firefox) { + std::wstring tmp; + if (GetDefaultFirefoxProfile(&tmp)) { + bool complete = true; + + StringMap settings; + tmp.append(L"prefs.js"); + if (ReadFirefoxPrefs(tmp, "network.proxy.", settings)) { + success = true; + if (settings.Get("type") == "1") { + if (ProxyListMatch(purl, settings.Get("no_proxies_on", "localhost, 127.0.0.1").c_str(), ',')) { + // 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") { + complete = success = false; + autoconfig_url = settings.Get("autoconfig_url").c_str(); + } else if (settings.Get("type") == "4") { + complete = success = false; + autoconfig = true; + } + } + if (complete) { // Otherwise fall through to IE autoproxy code + return success; + } + } + } +#endif // _TRY_FIREFOX + +#if _TRY_WINHTTP + if (!success) { + if (HMODULE hModWH = LoadLibrary(L"winhttp.dll")) { + pfnWinHttpOpen pWHO = reinterpret_cast<pfnWinHttpOpen>(GetProcAddress(hModWH, "WinHttpOpen")); + pfnWinHttpCloseHandle pWHCH = reinterpret_cast<pfnWinHttpCloseHandle>(GetProcAddress(hModWH, "WinHttpCloseHandle")); + pfnWinHttpGetProxyForUrl pWHGPFU = reinterpret_cast<pfnWinHttpGetProxyForUrl>(GetProcAddress(hModWH, "WinHttpGetProxyForUrl")); + pfnWinHttpGetIEProxyConfig pWHGIEPC = reinterpret_cast<pfnWinHttpGetIEProxyConfig>(GetProcAddress(hModWH, "WinHttpGetIEProxyConfigForCurrentUser")); + if (pWHO && pWHCH && pWHGPFU && pWHGIEPC) { + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG iecfg; + memset(&iecfg, 0, sizeof(iecfg)); + if (!use_firefox && !pWHGIEPC(&iecfg)) { + LOG_GLEM(LERROR,WINHTTP) << "WinHttpGetIEProxyConfigForCurrentUser"; + } else { + success = true; + if (!use_firefox) { + if (iecfg.fAutoDetect) { + autoconfig = true; + } + if (iecfg.lpszAutoConfigUrl) { + autoconfig_url = ToUtf8(iecfg.lpszAutoConfigUrl); + } + } + if (!long_operation) { + // Unless we perform this operation in the background, don't allow + // it to take a long time. + autoconfig = false; + } + if (autoconfig || !autoconfig_url.empty()) { + if (HINTERNET hWinHttp = pWHO(ToUtf16(agent).c_str(), + WINHTTP_ACCESS_TYPE_NO_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + 0)) { + WINHTTP_AUTOPROXY_OPTIONS options; + memset(&options, 0, sizeof(options)); + if (autoconfig) { + options.dwFlags |= WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags |= WINHTTP_AUTO_DETECT_TYPE_DHCP + | WINHTTP_AUTO_DETECT_TYPE_DNS_A; + } + std::wstring autoconfig_url16((ToUtf16)(autoconfig_url)); + if (!autoconfig_url.empty()) { + options.dwFlags |= WINHTTP_AUTOPROXY_CONFIG_URL; + options.lpszAutoConfigUrl = autoconfig_url16.c_str(); + } + options.fAutoLogonIfChallenged = TRUE; + WINHTTP_PROXY_INFO info; + memset(&info, 0, sizeof(info)); + + BOOL success = MyWinHttpGetProxyForUrl(pWHGPFU, + hWinHttp, ToUtf16(url).c_str(), &options, &info); + + if (!success) { + LOG_GLEM(LERROR,WINHTTP) << "WinHttpGetProxyForUrl"; + } else { + if (iecfg.lpszProxy) + GlobalFree(iecfg.lpszProxy); + if (iecfg.lpszProxyBypass) + GlobalFree(iecfg.lpszProxyBypass); + iecfg.lpszProxy = info.lpszProxy; + iecfg.lpszProxyBypass = info.lpszProxyBypass; + } + pWHCH(hWinHttp); + } + } + if (!ProxyListMatch(purl, ToUtf8(nonnull(iecfg.lpszProxyBypass)), ' ')) { + ParseProxy(ToUtf8(nonnull(iecfg.lpszProxy)), proxy); + } + if (iecfg.lpszAutoConfigUrl) + GlobalFree(iecfg.lpszAutoConfigUrl); + if (iecfg.lpszProxy) + GlobalFree(iecfg.lpszProxy); + if (iecfg.lpszProxyBypass) + GlobalFree(iecfg.lpszProxyBypass); + } + } + FreeLibrary(hModWH); + } + } +#endif // _TRY_WINHTTP + +#if _TRY_JSPROXY + if (!success) { + 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); + } + } +#endif // _TRY_JSPROXY + +#if _TRY_WM_FINDPROXY + if (!success) { + 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(); + } + } +#endif // _TRY_WM_FINDPROXY + +#if _TRY_IE_LAN_SETTINGS + if (!success) { + 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; + } + } +#endif // _TRY_IE_LAN_SETTINGS + +#if 0 + if (!success) { + 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); + } + } +#endif // 0 + + return success; +} diff --git a/third_party/libjingle/files/talk/base/proxydetect.h b/third_party/libjingle/files/talk/base/proxydetect.h new file mode 100644 index 0000000..3de2598 --- /dev/null +++ b/third_party/libjingle/files/talk/base/proxydetect.h @@ -0,0 +1,13 @@ +#ifndef _PROXYDETECT_H_ +#define _PROXYDETECT_H_ + +#include "talk/base/proxyinfo.h" + +// 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); + +#endif // _PROXYDETECT_H_ diff --git a/third_party/libjingle/files/talk/base/proxyinfo.cc b/third_party/libjingle/files/talk/base/proxyinfo.cc new file mode 100644 index 0000000..1d9c588 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/proxyinfo.h b/third_party/libjingle/files/talk/base/proxyinfo.h new file mode 100644 index 0000000..834ec4f --- /dev/null +++ b/third_party/libjingle/files/talk/base/proxyinfo.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_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 username; + CryptString password; + + ProxyInfo() : type(PROXY_NONE) { } +}; + +} // namespace talk_base + +#endif // TALK_BASE_PROXYINFO_H__ diff --git a/third_party/libjingle/files/talk/base/schanneladapter.cc b/third_party/libjingle/files/talk/base/schanneladapter.cc new file mode 100644 index 0000000..1a24c0c --- /dev/null +++ b/third_party/libjingle/files/talk/base/schanneladapter.cc @@ -0,0 +1,749 @@ +/* + * 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 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 +}; + +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) << " " << talk_base::ToUtf8(alg_name) + << " (" << alg_id << ")"; + } + } + } + + 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/third_party/libjingle/files/talk/base/schanneladapter.h b/third_party/libjingle/files/talk/base/schanneladapter.h new file mode 100644 index 0000000..a5ab7b3 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/scoped_ptr.h b/third_party/libjingle/files/talk/base/scoped_ptr.h new file mode 100644 index 0000000..9a9d378 --- /dev/null +++ b/third_party/libjingle/files/talk/base/scoped_ptr.h @@ -0,0 +1,38 @@ +// (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 <assert.h> // for assert +#include <stdlib.h> // for free() decl + +#ifdef _WIN32 +namespace std { using ::ptrdiff_t; }; +#endif // _WIN32 + +#include "base/scoped_ptr.h" + +#endif // #ifndef TALK_BASE_SCOPED_PTR_H__ diff --git a/third_party/libjingle/files/talk/base/sec_buffer.h b/third_party/libjingle/files/talk/base/sec_buffer.h new file mode 100644 index 0000000..585e27f --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/signalthread.cc b/third_party/libjingle/files/talk/base/signalthread.cc new file mode 100644 index 0000000..919d1c3 --- /dev/null +++ b/third_party/libjingle/files/talk/base/signalthread.cc @@ -0,0 +1,93 @@ +#include "talk/base/common.h" +#include "talk/base/signalthread.h" + +using namespace talk_base; + +/////////////////////////////////////////////////////////////////////////////// +// SignalThread +/////////////////////////////////////////////////////////////////////////////// + +SignalThread::SignalThread() +: main_(Thread::Current()), state_(kInit) +{ + worker_.parent_ = this; +} + +SignalThread::~SignalThread() { +} + +void SignalThread::SetPriority(ThreadPriority priority) { + ASSERT(main_->IsCurrent()); + ASSERT(kInit == state_); + worker_.SetPriority(priority); +} + +void SignalThread::Start() { + ASSERT(main_->IsCurrent()); + if (kInit == state_) { + state_ = kRunning; + OnWorkStart(); + worker_.Start(); + } else { + ASSERT(false); + } +} + +void SignalThread::Destroy() { + ASSERT(main_->IsCurrent()); + if ((kInit == state_) || (kComplete == state_)) { + delete this; + } else if (kRunning == state_) { + state_ = kStopping; + // A couple tricky issues here: + // 1) Thread::Stop() calls Join(), which we don't want... we just want + // to stop the MessageQueue, which causes ContinueWork() to return false. + // 2) OnWorkStop() must follow Stop(), so that when the thread wakes up + // due to OWS(), ContinueWork() will return false. + worker_.MessageQueue::Stop(); + OnWorkStop(); + } else { + ASSERT(false); + } +} + +void SignalThread::Release() { + ASSERT(main_->IsCurrent()); + if (kComplete == state_) { + delete this; + } else if (kRunning == state_) { + state_ = kReleasing; + } else { + // if (kInit == state_) use Destroy() + ASSERT(false); + } +} + +bool SignalThread::ContinueWork() { + ASSERT(worker_.IsCurrent()); + return worker_.ProcessMessages(0); +} + +void SignalThread::OnMessage(Message *msg) { + 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_) { + SignalWorkDone(this); + } + if (do_delete) { + delete this; + } + } +} + +void SignalThread::Run() { + DoWork(); + main_->Post(this, ST_MSG_WORKER_DONE); +} diff --git a/third_party/libjingle/files/talk/base/signalthread.h b/third_party/libjingle/files/talk/base/signalthread.h new file mode 100644 index 0000000..9e6bd05 --- /dev/null +++ b/third_party/libjingle/files/talk/base/signalthread.h @@ -0,0 +1,91 @@ +#ifndef _SIGNALTHREAD_H_ +#define _SIGNALTHREAD_H_ + +#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. +// 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 : protected MessageHandler { +public: + SignalThread(); + + // Context: Main Thread. Call before Start to change the worker's priority. + void 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. + void Destroy(); + + // 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(); + + // 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: + friend class Worker; + class Worker : public Thread { + public: + SignalThread* parent_; + virtual void Run() { parent_->Run(); } + }; + + void Run(); + + Thread* main_; + Worker worker_; + enum State { kInit, kRunning, kComplete, kStopping, kReleasing } state_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // _SIGNALTHREAD_H_ diff --git a/third_party/libjingle/files/talk/base/sigslot.h b/third_party/libjingle/files/talk/base/sigslot.h new file mode 100644 index 0000000..539db8a --- /dev/null +++ b/third_party/libjingle/files/talk/base/sigslot.h @@ -0,0 +1,2699 @@ +// 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 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 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 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 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 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 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 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 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 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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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(); + } + + 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) + { + m_connected_slots.erase(it); + // delete *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 _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 _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 _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 _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 _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 _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 _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 _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 _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/third_party/libjingle/files/talk/base/socket.h b/third_party/libjingle/files/talk/base/socket.h new file mode 100644 index 0000000..f81bbe8 --- /dev/null +++ b/third_party/libjingle/files/talk/base/socket.h @@ -0,0 +1,161 @@ +/* + * 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 +#define EWOULDBLOCK WSAEWOULDBLOCK +#define EINPROGRESS WSAEINPROGRESS +#define EALREADY WSAEALREADY +#define ENOTSOCK WSAENOTSOCK +#define EDESTADDRREQ WSAEDESTADDRREQ +#define EMSGSIZE WSAEMSGSIZE +#define EPROTOTYPE WSAEPROTOTYPE +#define ENOPROTOOPT WSAENOPROTOOPT +#define EPROTONOSUPPORT WSAEPROTONOSUPPORT +#define ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT +#define EOPNOTSUPP WSAEOPNOTSUPP +#define EPFNOSUPPORT WSAEPFNOSUPPORT +#define EAFNOSUPPORT WSAEAFNOSUPPORT +#define EADDRINUSE WSAEADDRINUSE +#define EADDRNOTAVAIL WSAEADDRNOTAVAIL +#define ENETDOWN WSAENETDOWN +#define ENETUNREACH WSAENETUNREACH +#define ENETRESET WSAENETRESET +#define ECONNABORTED WSAECONNABORTED +#define ECONNRESET WSAECONNRESET +#define ENOBUFS WSAENOBUFS +#define EISCONN WSAEISCONN +#define ENOTCONN WSAENOTCONN +#define ESHUTDOWN WSAESHUTDOWN +#define ETOOMANYREFS WSAETOOMANYREFS +#undef ETIMEDOUT // remove pthread.h's definition +#define ETIMEDOUT WSAETIMEDOUT +#define ECONNREFUSED WSAECONNREFUSED +#define ELOOP WSAELOOP +#undef ENAMETOOLONG // remove errno.h's definition +#define ENAMETOOLONG WSAENAMETOOLONG +#define EHOSTDOWN WSAEHOSTDOWN +#define EHOSTUNREACH WSAEHOSTUNREACH +#undef ENOTEMPTY // remove errno.h's definition +#define ENOTEMPTY WSAENOTEMPTY +#define EPROCLIM WSAEPROCLIM +#define EUSERS WSAEUSERS +#define EDQUOT WSAEDQUOT +#define ESTALE WSAESTALE +#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 + }; + 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/third_party/libjingle/files/talk/base/socketadapters.cc b/third_party/libjingle/files/talk/base/socketadapters.cc new file mode 100644 index 0000000..fcbc20b --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketadapters.cc @@ -0,0 +1,677 @@ +/* + * 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 +#include <winsock2.h> +#include <ws2tcpip.h> +#define _WINSOCKAPI_ +#include <windows.h> +#define SECURITY_WIN32 +#include <security.h> +#endif + +#include "talk/base/basicdefs.h" +#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 buffer_size) + : AsyncSocketAdapter(socket), buffer_size_(buffer_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(INFO) << "Recv: " << errno << " " << std::strerror(errno); + return; + } + + data_len_ += len; + + ProcessInput(buffer_, data_len_); +} + +/////////////////////////////////////////////////////////////////////////////// + +const uint8 SSL_SERVER_HELLO[] = { + 22,3,1,0,74,2,0,0,70,3,1,66,133,69,167,39,169,93,160, + 179,197,231,83,218,72,43,63,198,90,202,137,193,88,82, + 161,120,60,91,23,70,0,133,63,32,14,211,6,114,91,91, + 27,95,21,172,19,249,136,83,157,155,232,61,123,12,48, + 50,110,56,77,162,117,87,65,108,52,92,0,4,0 +}; + +const char SSL_CLIENT_HELLO[] = { + -128,70,1,3,1,0,45,0,0,0,16,1,0,-128,3,0,-128,7,0,-64,6,0,64,2,0, + -128,4,0,-128,0,0,4,0,-2,-1,0,0,10,0,-2,-2,0,0,9,0,0,100,0,0,98,0, + 0,3,0,0,6,31,23,12,-90,47,0,120,-4,70,85,46,-79,-125,57,-15,-22 +}; + +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... + int res = DirectSend(SSL_CLIENT_HELLO, sizeof(SSL_CLIENT_HELLO)); + ASSERT(res == sizeof(SSL_CLIENT_HELLO)); +} + +void AsyncSSLSocket::ProcessInput(char * data, size_t& len) { + if (len < sizeof(SSL_SERVER_HELLO)) + return; + + if (memcmp(SSL_SERVER_HELLO, data, sizeof(SSL_SERVER_HELLO)) != 0) { + Close(); + SignalCloseEvent(this, 0); // TODO: error code? + return; + } + + len -= sizeof(SSL_SERVER_HELLO); + if (len > 0) { + memmove(data, data + sizeof(SSL_SERVER_HELLO), 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); +} + +/////////////////////////////////////////////////////////////////////////////// + +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), state_(PS_ERROR), context_(0) { +} + +AsyncHttpsProxySocket::~AsyncHttpsProxySocket() { + delete context_; +} + +int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect(" + << proxy_.ToString() << ")"; + dest_ = addr; + if (dest_.port() != 80) { + BufferInput(true); + } + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const { + return dest_; +} + +int AsyncHttpsProxySocket::Close() { + headers_.clear(); + state_ = PS_ERROR; + delete context_; + context_ = 0; + return BufferedReadAdapter::Close(); +} + +void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) { + LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent"; + // TODO: Decide whether tunneling or not should be explicitly set, + // or indicated by destination port (as below) + if (dest_.port() == 80) { + 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?? +} + +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 or something 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) { + uint32 code; + if (sscanf(data, "HTTP/%*lu.%*lu %lu", &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), proxy_(proxy), user_(username), pass_(password), + state_(SS_ERROR) { +} + +int AsyncSocksProxySocket::Connect(const SocketAddress& addr) { + dest_ = addr; + BufferInput(true); + return BufferedReadAdapter::Connect(proxy_); +} + +SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const { + return dest_; +} + +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); +} + +/////////////////////////////////////////////////////////////////////////////// + +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, + static_cast<const char *>(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, + static_cast<const char *>(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, + static_cast<const char *>(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, + static_cast<const char *>(pv), res, hex_mode_, &lms_); + return res; +} + +void +LoggingSocketAdapter::OnConnectEvent(AsyncSocket * socket) { + LOG_V(level_) << label_ << " Connected"; + AsyncSocketAdapter::OnConnectEvent(socket); +} + +void +LoggingSocketAdapter::OnCloseEvent(AsyncSocket * socket, int err) { + LOG_V(level_) << label_ << " Closed with error: " << err; + AsyncSocketAdapter::OnCloseEvent(socket, err); +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/socketadapters.h b/third_party/libjingle/files/talk/base/socketadapters.h new file mode 100644 index 0000000..6cd6a8f --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketadapters.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_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 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_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class AsyncSSLSocket : public BufferedReadAdapter { +public: + AsyncSSLSocket(AsyncSocket* socket); + + virtual int Connect(const SocketAddress& addr); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void ProcessInput(char * data, size_t& len); +}; + +/////////////////////////////////////////////////////////////////////////////// + +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(); + + virtual int Connect(const SocketAddress& addr); + virtual SocketAddress GetRemoteAddress() const; + virtual int Close(); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + virtual void ProcessInput(char * data, size_t& len); + + 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_; + size_t content_length_; + int defer_error_; + bool expect_close_; + enum ProxyState { + 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_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +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; + +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: + SocketAddress proxy_, dest_; + std::string user_; + CryptString pass_; + enum SocksState { SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR } state_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +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); + +protected: + virtual void OnConnectEvent(AsyncSocket * socket); + virtual void OnCloseEvent(AsyncSocket * socket, int err); + +private: + LoggingSeverity level_; + std::string label_; + bool hex_mode_; + LogMultilineState lms_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_SOCKETADAPTERS_H__ diff --git a/third_party/libjingle/files/talk/base/socketaddress.cc b/third_party/libjingle/files/talk/base/socketaddress.cc new file mode 100644 index 0000000..e3a04d1 --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketaddress.cc @@ -0,0 +1,312 @@ +/* + * 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 <arpa/inet.h> +#include <netdb.h> +#endif + +#include <cstring> +#include <sstream> + +#include "talk/base/byteorder.h" +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/socketaddress.h" + +#ifdef WIN32 +#undef SetPort +int inet_aton(const char * cp, struct in_addr * inp) { + inp->s_addr = inet_addr(cp); + return (inp->s_addr == INADDR_NONE) ? 0 : 1; +} +#endif // WIN32 + +#ifdef _DEBUG +#define DISABLE_DNS 0 +#else // !_DEBUG +#define DISABLE_DNS 0 +#endif // !_DEBUG + +namespace talk_base { + +SocketAddress::SocketAddress() { + Clear(); +} + +SocketAddress::SocketAddress(const std::string& hostname, int port, bool use_dns) { + SetIP(hostname, use_dns); + 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; +} + +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; +} + +bool SocketAddress::SetIP(const std::string& hostname, bool use_dns) { + hostname_ = hostname; + ip_ = 0; + return Resolve(true, use_dns); +} + +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::IsAny() const { + return (ip_ == 0); +} + +bool SocketAddress::IsLocalIP() const { + return (ip_ >> 24) == 127; +} + +bool SocketAddress::IsPrivateIP() const { + return ((ip_ >> 24) == 127) || + ((ip_ >> 24) == 10) || + ((ip_ >> 20) == ((172 << 4) | 1)) || + ((ip_ >> 16) == ((192 << 8) | 168)); +} + +bool SocketAddress::IsUnresolved() const { + return IsAny() && !hostname_.empty(); +} + +bool SocketAddress::Resolve(bool force, bool use_dns) { + if (hostname_.empty()) { + // nothing to resolve + } else if (!force && !IsAny()) { + // already resolved + } else if (uint32 ip = StringToIP(hostname_, use_dns)) { + ip_ = ip; + } else { + return false; + } + return true; +} + +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_); +} + +void SocketAddress::Write_(char* buf, int len) const { + // TODO: Depending on how this is used, we may want/need to write hostname + ASSERT((size_t)len >= Size_()); + reinterpret_cast<uint32*>(buf)[0] = ip_; + buf += sizeof(ip_); + reinterpret_cast<uint16*>(buf)[0] = port_; +} + +void SocketAddress::Read_(const char* buf, int len) { + ASSERT((size_t)len >= Size_()); + ip_ = reinterpret_cast<const uint32*>(buf)[0]; + buf += sizeof(ip_); + port_ = reinterpret_cast<const uint16*>(buf)[0]; +} + +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_); + } +} + +void SocketAddress::FromSockAddr(const sockaddr_in& saddr) { + SetIP(NetworkToHost32(saddr.sin_addr.s_addr)); + SetPort(NetworkToHost16(saddr.sin_port)); +} + +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(); +} + +uint32 SocketAddress::StringToIP(const std::string& hostname, bool use_dns) { + uint32 ip = 0; + in_addr addr; + if (inet_aton(hostname.c_str(), &addr) != 0) { + ip = NetworkToHost32(addr.s_addr); + } else if (use_dns) { + // Note: this is here so we can spot spurious DNS resolutions for a while + LOG(INFO) << "=== DNS RESOLUTION (" << hostname << ") ==="; +#if DISABLE_DNS + LOG(WARNING) << "*** DNS DISABLED ***"; +#if WIN32 + WSASetLastError(WSAHOST_NOT_FOUND); +#endif // WIN32 +#endif // DISABLE_DNS + if (hostent * pHost = gethostbyname(hostname.c_str())) { + ip = NetworkToHost32(*reinterpret_cast<uint32 *>(pHost->h_addr_list[0])); + } else { +#if WIN32 + LOG(LS_ERROR) << "gethostbyname error: " << WSAGetLastError(); +#else + LOG(LS_ERROR) << "gethostbyname error: " << strerror(h_errno); +#endif + } + LOG(INFO) << hostname << " resolved to " << IPToString(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; + + if (hostent * pHost = gethostbyname(hostname.c_str())) { + 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); + } + return !ips.empty(); + } +#if WIN32 + LOG(LS_ERROR) << "gethostbyname error: " << WSAGetLastError(); +#else + LOG(LS_ERROR) << "gethostbyname error: " << strerror(h_errno); +#endif + return false; +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/socketaddress.h b/third_party/libjingle/files/talk/base/socketaddress.h new file mode 100644 index 0000000..7fdc22f --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketaddress.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_SOCKETADDRESS_H__ +#define TALK_BASE_SOCKETADDRESS_H__ + +#include <string> +#include <vector> +#include "talk/base/basictypes.h" +struct sockaddr_in; + +#undef SetPort + +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 missing / unknown 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 = 0, bool use_dns = true); + + // 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 missing / unknown address. + void Clear(); + + // 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. + // Calls Resolve and returns the result. + bool SetIP(const std::string& hostname, bool use_dns = true); + + // 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 IP address. + uint32 ip() const; + + // Returns the port part of this address. + uint16 port() const; + + // Returns the hostname + const std::string& hostname() const { return hostname_; }; + + // Returns the IP address in dotted form. + std::string IPAsString() const; + + // Returns the port as a string + std::string PortAsString() const; + + // Returns a display version of the IP/port. + std::string ToString() const; + + // Determines whether this represents a missing / any address. + bool IsAny() const; + + // Synomym for missing / any. + bool IsNil() const { return IsAny(); } + + // Determines whether the IP address refers to the local host, i.e. within + // the range 127.0.0.0/8. + 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 IsUnresolved() const; + + // Attempt to resolve a hostname to IP address. + // Returns false if resolution is required but failed. + // 'force' will cause re-resolution of hostname. + // + bool Resolve(bool force = false, bool use_dns = true); + + // 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; + + // Deteremines 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. + void Write_(char* buf, int len) const; + + // Reads this address from the given buffer. + void Read_(const char* buf, int len); + + // Convert to and from sockaddr_in + void ToSockAddr(sockaddr_in* saddr) const; + void 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. + // Without 'use_dns', only dotted names (A.B.C.D) are resolved. + static uint32 StringToIP(const std::string& str, bool use_dns = true); + + // 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/third_party/libjingle/files/talk/base/socketaddresspair.cc b/third_party/libjingle/files/talk/base/socketaddresspair.cc new file mode 100644 index 0000000..7f190a9 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/socketaddresspair.h b/third_party/libjingle/files/talk/base/socketaddresspair.h new file mode 100644 index 0000000..10f5d304 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/socketfactory.h b/third_party/libjingle/files/talk/base/socketfactory.h new file mode 100644 index 0000000..2a4aee2 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/socketpool.cc b/third_party/libjingle/files/talk/base/socketpool.cc new file mode 100644 index 0000000..614ce89 --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketpool.cc @@ -0,0 +1,263 @@ +/* + * 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 <errno.h> + +#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" + +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.ToString() << ")"; + 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.ToString() << ")"; + 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.ToString() << ")"; + // 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() { + for (size_t i = 0; i < used_.size(); ++i) { + delete used_[i]; + } +} + +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) { + used_.push_back(stream); +} + +////////////////////////////////////////////////////////////////////// +// ReuseSocketPool +////////////////////////////////////////////////////////////////////// + +ReuseSocketPool::ReuseSocketPool(SocketFactory* factory, AsyncSocket* socket) + : factory_(factory), stream_(NULL) { + stream_ = socket ? new SocketStream(socket) : NULL; +} + +ReuseSocketPool::~ReuseSocketPool() { + delete stream_; +} + +void +ReuseSocketPool::setSocket(AsyncSocket* socket) { + ASSERT(false); // TODO: need ref-counting to make this work + delete stream_; + stream_ = socket ? new SocketStream(socket) : NULL; +} + +StreamInterface* +ReuseSocketPool::RequestConnectedStream(const SocketAddress& remote, int* err) { + if (!stream_) { + LOG(LS_INFO) << "ReuseSocketPool - 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) && + (stream_->GetSocket()->GetRemoteAddress() == remote)) { + LOG(LS_INFO) << "ReuseSocketPool - Reusing connection to: " + << remote.ToString(); + } else { + stream_->Close(); + if ((stream_->GetSocket()->Connect(remote) != 0) + && !stream_->GetSocket()->IsBlocking()) { + if (err) + *err = stream_->GetSocket()->GetError(); + return NULL; + } else { + LOG(LS_INFO) << "ReuseSocketPool - Opening connection to: " + << remote.ToString(); + } + } + if (err) + *err = 0; + return stream_; +} + +void +ReuseSocketPool::ReturnConnectedStream(StreamInterface* stream) { + // Note: this might not be true with the advent of setSocket + ASSERT(stream == stream_); +} + +/////////////////////////////////////////////////////////////////////////////// +// 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)) { + if (recycle_bin_.empty()) { + return new LoggingAdapter(stream, level_, label_, binary_mode_); + } + LoggingAdapter* logging = recycle_bin_.front(); + recycle_bin_.pop_front(); + 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/third_party/libjingle/files/talk/base/socketpool.h b/third_party/libjingle/files/talk/base/socketpool.h new file mode 100644 index 0000000..edeebaa --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketpool.h @@ -0,0 +1,161 @@ +/* + * 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 <vector> +#include "talk/base/logging.h" +#include "talk/base/sigslot.h" + +namespace talk_base { + +class AsyncSocket; +class LoggingAdapter; +class SocketAddress; +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 PTcpSocket every time +////////////////////////////////////////////////////////////////////// + +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_; + std::vector<StreamInterface*> used_; +}; + +////////////////////////////////////////////////////////////////////// +// ReuseSocketPool // +///////////////////// +// Pass a PTcpSocket chain to the constructor, and if the connection +// is still open, it will be reused. +////////////////////////////////////////////////////////////////////// + +class ReuseSocketPool : public StreamPool { +public: + ReuseSocketPool(SocketFactory* factory, AsyncSocket* socket = 0); + virtual ~ReuseSocketPool(); + + void setSocket(AsyncSocket* socket); + + // StreamPool Interface + virtual StreamInterface* RequestConnectedStream(const SocketAddress& remote, + int* err); + virtual void ReturnConnectedStream(StreamInterface* stream); + +private: + SocketFactory* factory_; + SocketStream* stream_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// 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/third_party/libjingle/files/talk/base/socketserver.h b/third_party/libjingle/files/talk/base/socketserver.h new file mode 100644 index 0000000..e06dd6b --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketserver.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 TALK_BASE_SOCKETSERVER_H__ +#define TALK_BASE_SOCKETSERVER_H__ + +#include "talk/base/socketfactory.h" + +namespace talk_base { + +const int kForever = -1; + +// 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: + + // 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/third_party/libjingle/files/talk/base/socketstream.h b/third_party/libjingle/files/talk/base/socketstream.h new file mode 100644 index 0000000..4d56b75 --- /dev/null +++ b/third_party/libjingle/files/talk/base/socketstream.h @@ -0,0 +1,153 @@ +/* + * 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_SOCKET_STREAM_H__ +#define TALK_BASE_SOCKET_STREAM_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: + SocketStream(AsyncSocket* socket) : socket_(NULL) { + Attach(socket); + } + virtual ~SocketStream() { delete socket_; } + + void 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* 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; + } + + AsyncSocket* GetSocket() { return socket_; } + + virtual StreamState 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; + } + } + + virtual StreamResult 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; + } + + virtual StreamResult 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; + } + + virtual void Close() { ASSERT(socket_ != NULL); socket_->Close(); } + + virtual bool GetSize(size_t* size) const { return false; } + virtual bool ReserveSize(size_t size) { return true; } + virtual bool Rewind() { return false; } + + private: + void OnConnectEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_OPEN | SE_READ | SE_WRITE, 0); + } + void OnReadEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_READ, 0); + } + void OnWriteEvent(AsyncSocket* socket) { + ASSERT(socket == socket_); + SignalEvent(this, SE_WRITE, 0); + } + void OnCloseEvent(AsyncSocket* socket, int err) { + ASSERT(socket == socket_); + SignalEvent(this, SE_CLOSE, err); + } + + AsyncSocket* socket_; + + DISALLOW_EVIL_CONSTRUCTORS(SocketStream); +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_SOCKET_STREAM_H__ diff --git a/third_party/libjingle/files/talk/base/ssladapter.cc b/third_party/libjingle/files/talk/base/ssladapter.cc new file mode 100644 index 0000000..68fe038 --- /dev/null +++ b/third_party/libjingle/files/talk/base/ssladapter.cc @@ -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. + */ + +#include "talk/base/ssladapter.h" + +#if !defined(SSL_USE_SCHANNEL) && !defined(SSL_USE_OPENSSL) +#ifdef WIN32 +#define SSL_USE_SCHANNEL 1 +#else // !WIN32 +#define SSL_USE_OPENSSL 1 +#endif // !WIN32 +#endif + +#if SSL_USE_SCHANNEL +#include "schanneladapter.h" +namespace talk_base { + typedef SChannelAdapter DefaultSSLAdapter; +} +#endif // SSL_USE_SCHANNEL + +#if SSL_USE_OPENSSL +#include <openssl/crypto.h> +#include <openssl/rand.h> +#include <openssl/ssl.h> +#include "openssladapter.h" +namespace talk_base { + typedef OpenSSLAdapter DefaultSSLAdapter; +} +#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; +}; + +#endif // SSL_USE_OPENSSL + +/////////////////////////////////////////////////////////////////////////////// + +namespace talk_base { + +SSLAdapter* +SSLAdapter::Create(AsyncSocket* socket) { + return new DefaultSSLAdapter(socket); +} + +/////////////////////////////////////////////////////////////////////////////// + +#if SSL_USE_OPENSSL + + + +// 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; +} + +bool InitializeSSL() { + if (!InitializeSSLThread() || !SSL_library_init()) + return false; + SSL_load_error_strings(); + ERR_load_BIO_strings(); + OpenSSL_add_all_algorithms(); + RAND_poll(); + return true; +} + +bool 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 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; +} + +#else // !SSL_USE_OPENSSL + +bool InitializeSSL() { + return true; +} + +bool InitializeSSLThread() { + return true; +} + +bool CleanupSSL() { + return true; +} + +#endif // !SSL_USE_OPENSSL + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/ssladapter.h b/third_party/libjingle/files/talk/base/ssladapter.h new file mode 100644 index 0000000..0f0155c --- /dev/null +++ b/third_party/libjingle/files/talk/base/ssladapter.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 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_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Call this on the main thread, before using SSL. +// Call CleanupSSLThread when finished with SSL. +bool InitializeSSL(); + +// 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/third_party/libjingle/files/talk/base/stl_decl.h b/third_party/libjingle/files/talk/base/stl_decl.h new file mode 100644 index 0000000..193e7af --- /dev/null +++ b/third_party/libjingle/files/talk/base/stl_decl.h @@ -0,0 +1,84 @@ +/* + * 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_STL_DECL_H__ +#define TALK_BASE_STL_DECL_H__ + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 +#pragma warning(disable:4786) +#endif + +#include <sys/types.h> + +namespace std { + template <class Key> struct hash; + template <class Key> struct equal_to; + template <class Key> struct less; + template <class T> class allocator; + template <class Key, class Val, + class Compare, + class Alloc> class map; + template <class T, class Alloc> class vector; + template <class T, class Alloc> class list; + template <class T, class Alloc> class slist; + template <class T, class Sequence> class stack; + template <class T, class Sequence> class queue; + template <class T, class Sequence, class Compare> class priority_queue; + template <class T1, class T2> struct pair; + template <class Key, class Compare, class Alloc> class set; +} + +///////////////////////////////////////////////////////////////////////////// +// Workaround declaration problem with defaults +///////////////////////////////////////////////////////////////////////////// + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // 1200 == VC++ 6.0 + +#define STD_MAP(T1, T2) \ + std::map<T1 , T2, std::less<T1>, std::allocator<T2> > + +#define STD_VECTOR(T1) \ + std::vector<T1, std::allocator<T1> > + +#define STD_SET(T1) \ + std::set<T1, std::less<T1>, std::allocator<T1> > + +#else + +#define STD_MAP(T1, T2) \ + std::map<T1, T2, std::less<T1>, std::allocator<std::pair<const T1, T2 > > > + +#define STD_VECTOR(T1) \ + std::vector<T1, std::allocator<T1> > + +#define STD_SET(T1) \ + std::set<T1, std::less<T1>, std::allocator<T1> > + +#endif + + +#endif // TALK_BASE_STL_DECL_H__ diff --git a/third_party/libjingle/files/talk/base/stream.cc b/third_party/libjingle/files/talk/base/stream.cc new file mode 100644 index 0000000..50d49a6 --- /dev/null +++ b/third_party/libjingle/files/talk/base/stream.cc @@ -0,0 +1,664 @@ +/* + * 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 <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/stream.h" +#include "talk/base/stringencode.h" + +#ifdef WIN32 +#include "talk/base/win32.h" +#define fileno _fileno +#endif + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// + +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) { + 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; +} + +/////////////////////////////////////////////////////////////////////////////// +// 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; +} + +/////////////////////////////////////////////////////////////////////////////// +// 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() { +} + +bool NullStream::GetSize(size_t* size) const { + if (size) + *size = 0; + return true; +} + +bool NullStream::ReserveSize(size_t size) { + return true; +} + +bool NullStream::Rewind() { + return false; +} + +/////////////////////////////////////////////////////////////////////////////// +// FileStream +/////////////////////////////////////////////////////////////////////////////// + +FileStream::FileStream() : file_(NULL) { +} + +FileStream::~FileStream() { + FileStream::Close(); +} + +bool FileStream::Open(const std::string& filename, const char* mode) { + Close(); +#ifdef WIN32 + int filenamelen = MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), + filename.length() + 1, NULL, 0); + int modelen = MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); + wchar_t *wfilename = new wchar_t[filenamelen+4]; // 4 for "\\?\" + wchar_t *wfilename_dest = wfilename; + wchar_t *wmode = new wchar_t[modelen]; + + if (!filename.empty() && (filename[0] != '\\')) { + wcscpy(wfilename, L"\\\\?\\"); + wfilename_dest = wfilename + 4; + } + + if ((MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), filename.length() + 1, + wfilename_dest, filenamelen) > 0) && + (MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, modelen) > 0)) { + file_ = _wfopen(wfilename, wmode); + } else { + file_ = NULL; + } + + delete[] wfilename; + delete[] wmode; +#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 + int filenamelen = MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), + filename.length() + 1, NULL, 0); + int modelen = MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); + wchar_t *wfilename = new wchar_t[filenamelen+4]; // 4 for "\\?\" + wchar_t *wfilename_dest = wfilename; + wchar_t *wmode = new wchar_t[modelen]; + + if (!filename.empty() && (filename[0] != '\\')) { + wcscpy(wfilename, L"\\\\?\\"); + wfilename_dest = wfilename + 4; + } + + if ((MultiByteToWideChar(CP_UTF8, 0, filename.c_str(), filename.length() + 1, + wfilename_dest, filenamelen) > 0) && + (MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, modelen) > 0)) { + file_ = _wfsopen(wfilename, wmode, shflag); + } else { + file_ = NULL; + } + + delete[] wfilename; + delete[] wmode; +#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_) { + fclose(file_); + 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(position != NULL); + if (!file_ || !position) + return false; + long result = ftell(file_); + if (result < 0) + return false; + *position = result; + return true; +} + +bool FileStream::GetSize(size_t * size) const { + ASSERT(size != NULL); + if (!file_ || !size) + return false; + struct stat file_stats; + if (fstat(fileno(file_), &file_stats) != 0) + return false; + *size = file_stats.st_size; + 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; +} + +int FileStream::Flush() { + if (file_) { + return fflush (file_); + } + // try to flush empty file? + ASSERT(false); + return 0; +} +/////////////////////////////////////////////////////////////////////////////// + + +MemoryStream::MemoryStream() + : allocated_length_(0), buffer_(NULL), data_length_(0), seek_position_(0) { +} + +MemoryStream::MemoryStream(const char* data) + : allocated_length_(0), buffer_(NULL), data_length_(0), seek_position_(0) { + SetContents(data, strlen(data)); +} + +MemoryStream::MemoryStream(const char* data, size_t length) + : allocated_length_(0), buffer_(NULL), data_length_(0), seek_position_(0) { + SetContents(data, length); +} + +MemoryStream::~MemoryStream() { + delete [] buffer_; +} + +void MemoryStream::SetContents(const char* data, size_t length) { + delete [] buffer_; + data_length_ = allocated_length_ = length; + buffer_ = new char[allocated_length_]; + memcpy(buffer_, data, data_length_); +} + +StreamState MemoryStream::GetState() const { + return SS_OPEN; +} + +StreamResult MemoryStream::Read(void *buffer, size_t bytes, + size_t *bytes_read, int *error) { + if (seek_position_ >= data_length_) { + // At end of stream + if (error) { + *error = EOF; + } + return SR_EOS; + } + + size_t remaining_length = data_length_ - seek_position_; + if (bytes > remaining_length) { + // Read partial buffer + bytes = remaining_length; + } + memcpy(buffer, &buffer_[seek_position_], bytes); + seek_position_ += bytes; + if (bytes_read) { + *bytes_read = bytes; + } + return SR_SUCCESS; +} + +StreamResult MemoryStream::Write(const void *buffer, + size_t bytes, size_t *bytes_written, int *error) { + StreamResult sr = SR_SUCCESS; + int error_value = 0; + size_t bytes_written_value = 0; + + size_t new_position = seek_position_ + bytes; + if (new_position > allocated_length_) { + // Increase buffer size to the larger of: + // a) new position rounded up to next 256 bytes + // b) double the previous length + size_t new_allocated_length = _max((new_position | 0xFF) + 1, + allocated_length_ * 2); + if (char* new_buffer = new char[new_allocated_length]) { + memcpy(new_buffer, buffer_, data_length_); + delete [] buffer_; + buffer_ = new_buffer; + allocated_length_ = new_allocated_length; + } else { + error_value = ENOMEM; + sr = SR_ERROR; + } + } + + if (sr == SR_SUCCESS) { + bytes_written_value = bytes; + memcpy(&buffer_[seek_position_], buffer, bytes); + seek_position_ = new_position; + if (data_length_ < seek_position_) { + data_length_ = seek_position_; + } + } + + if (bytes_written) { + *bytes_written = bytes_written_value; + } + if (error) { + *error = error_value; + } + + return sr; +} + +void MemoryStream::Close() { + // nothing to do +} + +bool MemoryStream::SetPosition(size_t position) { + if (position <= data_length_) { + seek_position_ = position; + return true; + } + return false; +} + +bool MemoryStream::GetPosition(size_t *position) const { + if (!position) { + return false; + } + *position = seek_position_; + return true; +} + +bool MemoryStream::GetSize(size_t *size) const { + if (!size) { + return false; + } + *size = data_length_; + return true; +} + +bool MemoryStream::ReserveSize(size_t size) { + if (allocated_length_ >= size) + return true; + + if (char* new_buffer = new char[size]) { + memcpy(new_buffer, buffer_, data_length_); + delete [] buffer_; + buffer_ = new_buffer; + allocated_length_ = size; + return true; + } + + return false; +} + +/////////////////////////////////////////////////////////////////////////////// + +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink) { + ASSERT(buffer_len > 0); + + StreamResult result; + size_t count, read_pos, write_pos; + + bool end_of_stream = false; + do { + // Read until buffer is full, end of stream, or error + read_pos = 0; + do { + 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) { + return result; + } else { + read_pos += count; + } + } while (!end_of_stream && (read_pos < buffer_len)); + + // Write until buffer is empty, or error (including end of stream) + write_pos = 0; + do { + result = sink->Write(buffer + write_pos, read_pos - write_pos, + &count, NULL); + if (result != SR_SUCCESS) + return result; + + write_pos += count; + } while (write_pos < read_pos); + } while (!end_of_stream); + + return SR_SUCCESS; +} + +/////////////////////////////////////////////////////////////////////////////// + +LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode) +: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) +{ + label_.append("["); + 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, + static_cast<const char *>(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, + static_cast<const char *>(data), *written, hex_mode_, &lms_); + } + return result; +} + +void LoggingAdapter::Close() { + 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) { + 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 = talk_base::_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::GetSize(size_t* size) const { + ASSERT(size != NULL); + *size = str_.size(); + return true; +} + +bool StringStream::ReserveSize(size_t size) { + if (read_only_) + return false; + str_.reserve(size); + return true; +} + +bool StringStream::Rewind() { + read_pos_ = 0; + return true; +} + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/stream.h b/third_party/libjingle/files/talk/base/stream.h new file mode 100644 index 0000000..cb91bb74 --- /dev/null +++ b/third_party/libjingle/files/talk/base/stream.h @@ -0,0 +1,396 @@ +/* + * 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_STREAM_H__ +#define TALK_BASE_STREAM_H__ + +#include "talk/base/basictypes.h" +#include "talk/base/logging.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. +/////////////////////////////////////////////////////////////////////////////// + +// 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 StreamInterface { + 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; + + // Return the number of bytes that will be returned by Read, if known. + virtual bool GetSize(size_t* size) const = 0; + + // 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) = 0; + + // Returns true if stream could be repositioned to the beginning. + virtual bool Rewind() = 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); + + // 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; + + protected: + StreamInterface() { } + + 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. Note that the adapter will delete the adapted stream. +/////////////////////////////////////////////////////////////////////////////// + +class StreamAdapterInterface : public StreamInterface, + public sigslot::has_slots<> { + public: + explicit StreamAdapterInterface(StreamInterface* stream) { + Attach(stream); + } + + 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(); + } + virtual bool GetSize(size_t* size) const { + return stream_->GetSize(size); + } + virtual bool ReserveSize(size_t size) { + return stream_->ReserveSize(size); + } + virtual bool Rewind() { + return stream_->Rewind(); + } + + void Attach(StreamInterface* stream) { + if (NULL != stream_.get()) + stream_->SignalEvent.disconnect(this); + stream_.reset(stream); + if (NULL != stream_.get()) + stream_->SignalEvent.connect(this, &StreamAdapterInterface::OnEvent); + } + StreamInterface* Detach() { + if (NULL == stream_.get()) + return NULL; + stream_->SignalEvent.disconnect(this); + return stream_.release(); + } + + protected: + // 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); + } + + private: + scoped_ptr<StreamInterface> stream_; + 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); +}; + +/////////////////////////////////////////////////////////////////////////////// +// 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(); + virtual bool GetSize(size_t* size) const; + virtual bool ReserveSize(size_t size); + virtual bool Rewind(); +}; + +/////////////////////////////////////////////////////////////////////////////// +// 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 GetSize(size_t* size) const; + virtual bool ReserveSize(size_t size); + virtual bool Rewind() { return SetPosition(0); } + + bool SetPosition(size_t position); + bool GetPosition(size_t* position) const; + int Flush(); + static bool GetSize(const std::string& filename, size_t* size); + + private: + FILE* file_; + DISALLOW_EVIL_CONSTRUCTORS(FileStream); +}; + +/////////////////////////////////////////////////////////////////////////////// +// MemoryStream is a simple implementation of a StreamInterface over in-memory +// data. It does not support asynchronous notification. +/////////////////////////////////////////////////////////////////////////////// + +class MemoryStream : public StreamInterface { + public: + MemoryStream(); + // Pre-populate stream with the provided data. + MemoryStream(const char* data); + MemoryStream(const char* data, size_t length); + virtual ~MemoryStream(); + + 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 GetSize(size_t* size) const; + virtual bool ReserveSize(size_t size); + virtual bool Rewind() { return SetPosition(0); } + + char* GetBuffer() { return buffer_; } + const char* GetBuffer() const { return buffer_; } + bool SetPosition(size_t position); + bool GetPosition(size_t* position) const; + + private: + void SetContents(const char* data, size_t length); + + size_t allocated_length_; + char* buffer_; + size_t data_length_; + size_t seek_position_; + + private: + DISALLOW_EVIL_CONSTRUCTORS(MemoryStream); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class LoggingAdapter : public StreamAdapterInterface { +public: + LoggingAdapter(StreamInterface* stream, LoggingSeverity level, + const std::string& label, bool hex_mode = false); + + 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: + StringStream(std::string& str); + 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 GetSize(size_t* size) const; + virtual bool ReserveSize(size_t size); + virtual bool Rewind(); + +private: + std::string& str_; + size_t read_pos_; + bool read_only_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// 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. + +StreamResult Flow(StreamInterface* source, + char* buffer, size_t buffer_len, + StreamInterface* sink); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_STREAM_H__ diff --git a/third_party/libjingle/files/talk/base/streamutils.cc b/third_party/libjingle/files/talk/base/streamutils.cc new file mode 100644 index 0000000..52e6da7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/streamutils.cc @@ -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. + */ + + +#include "talk/base/common.h" +#include "talk/base/streamutils.h" + +/////////////////////////////////////////////////////////////////////////////// +// TODO: Extend so that one side can close, and other side can send +// buffered data. + +StreamRelay::StreamRelay(talk_base::StreamInterface* s1, + talk_base::StreamInterface* s2, + size_t buffer_size) : buffer_size_(buffer_size) { + dir_[0].stream = s1; + dir_[1].stream = s2; + + ASSERT(s1->GetState() != talk_base::SS_CLOSED); + ASSERT(s2->GetState() != talk_base::SS_CLOSED); + + for (size_t i=0; i<2; ++i) { + dir_[i].stream->SignalEvent.connect(this, &StreamRelay::OnEvent); + dir_[i].buffer = new char[buffer_size_]; + dir_[i].data_len = 0; + } +} + +StreamRelay::~StreamRelay() { + for (size_t i=0; i<2; ++i) { + delete dir_[i].stream; + delete [] dir_[i].buffer; + } +} + +void +StreamRelay::Circulate() { + int error = 0; + if (!Flow(0, &error) || !Flow(1, &error)) { + Close(); + SignalClosed(this, error); + } +} + +void +StreamRelay::Close() { + for (size_t i=0; i<2; ++i) { + dir_[i].stream->SignalEvent.disconnect(this); + dir_[i].stream->Close(); + } +} + +bool +StreamRelay::Flow(int read_index, int* error) { + Direction& reader = dir_[read_index]; + Direction& writer = dir_[Complement(read_index)]; + + bool progress; + do { + progress = false; + + while (reader.stream->GetState() == talk_base::SS_OPEN) { + size_t available = buffer_size_ - reader.data_len; + if (available == 0) + break; + + *error = 0; + size_t read = 0; + talk_base::StreamResult result + = reader.stream->Read(reader.buffer + reader.data_len, available, + &read, error); + if ((result == talk_base::SR_BLOCK) || (result == talk_base::SR_EOS)) + break; + + if (result == talk_base::SR_ERROR) + return false; + + progress = true; + ASSERT((read > 0) && (read <= available)); + reader.data_len += read; + } + + size_t total_written = 0; + while (writer.stream->GetState() == talk_base::SS_OPEN) { + size_t available = reader.data_len - total_written; + if (available == 0) + break; + + *error = 0; + size_t written = 0; + talk_base::StreamResult result + = writer.stream->Write(reader.buffer + total_written, + available, &written, error); + if ((result == talk_base::SR_BLOCK) || (result == talk_base::SR_EOS)) + break; + + if (result == talk_base::SR_ERROR) + return false; + + progress = true; + ASSERT((written > 0) && (written <= available)); + total_written += written; + } + + reader.data_len -= total_written; + if (reader.data_len > 0) { + memmove(reader.buffer, reader.buffer + total_written, reader.data_len); + } + } while (progress); + + return true; +} + +void StreamRelay::OnEvent(talk_base::StreamInterface* stream, int events, + int error) { + int index = Index(stream); + + // Note: In the following cases, we are treating the open event as both + // readable and writeable, for robustness. It won't hurt if we are wrong. + + if ((events & talk_base::SE_OPEN | talk_base::SE_READ) + && !Flow(index, &error)) { + events = talk_base::SE_CLOSE; + } + + if ((events & talk_base::SE_OPEN | talk_base::SE_WRITE) + && !Flow(Complement(index), &error)) { + events = talk_base::SE_CLOSE; + } + + if (events & talk_base::SE_CLOSE) { + Close(); + SignalClosed(this, error); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// StreamCounter - counts the number of bytes which are transferred in either +// direction. +/////////////////////////////////////////////////////////////////////////////// + +StreamCounter::StreamCounter(talk_base::StreamInterface* stream) + : StreamAdapterInterface(stream), count_(0) { +} + +talk_base::StreamResult StreamCounter::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + size_t tmp; + if (!read) + read = &tmp; + talk_base::StreamResult result + = StreamAdapterInterface::Read(buffer, buffer_len, + read, error); + if (result == talk_base::SR_SUCCESS) + count_ += *read; + SignalUpdateByteCount(count_); + return result; +} + +talk_base::StreamResult StreamCounter::Write( + const void* data, size_t data_len, size_t* written, int* error) { + size_t tmp; + if (!written) + written = &tmp; + talk_base::StreamResult result + = StreamAdapterInterface::Write(data, data_len, written, error); + if (result == talk_base::SR_SUCCESS) + count_ += *written; + SignalUpdateByteCount(count_); + return result; +} diff --git a/third_party/libjingle/files/talk/base/streamutils.h b/third_party/libjingle/files/talk/base/streamutils.h new file mode 100644 index 0000000..7bb07a3 --- /dev/null +++ b/third_party/libjingle/files/talk/base/streamutils.h @@ -0,0 +1,94 @@ +/* + * 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_APP_STREAMUTILS_H__ +#define TALK_APP_STREAMUTILS_H__ + +#include "talk/base/sigslot.h" +#include "talk/base/stream.h" + +/////////////////////////////////////////////////////////////////////////////// +// StreamRelay - acts as an intermediary between two asynchronous streams, +// reading from one stream and writing to the other, using a pre-specified +// amount of buffering in both directions. +/////////////////////////////////////////////////////////////////////////////// + +class StreamRelay : public sigslot::has_slots<> { +public: + StreamRelay(talk_base::StreamInterface* s1, + talk_base::StreamInterface* s2, size_t buffer_size); + virtual ~StreamRelay(); + + void Circulate(); // Simulate events to get things flowing + void Close(); + + sigslot::signal2<StreamRelay*, int> SignalClosed; + +private: + inline int Index(talk_base::StreamInterface* s) const + { return (s == dir_[1].stream); } + inline int Complement(int index) const { return (1-index); } + + bool Flow(int read_index, int* error); + void OnEvent(talk_base::StreamInterface* stream, int events, int error); + + struct Direction { + talk_base::StreamInterface* stream; + char* buffer; + size_t data_len; + }; + Direction dir_[2]; + size_t buffer_size_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// StreamCounter - counts the number of bytes which are transferred in either +// direction. +/////////////////////////////////////////////////////////////////////////////// + +class StreamCounter : public talk_base::StreamAdapterInterface { + public: + explicit StreamCounter(talk_base::StreamInterface* stream); + + inline void ResetByteCount() { count_ = 0; } + inline size_t GetByteCount() const { return count_; } + + sigslot::signal1<size_t> SignalUpdateByteCount; + + // StreamAdapterInterface + virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual talk_base::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + + private: + size_t count_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +#endif // TALK_APP_STREAMUTILS_H__ diff --git a/third_party/libjingle/files/talk/base/stringdigest.cc b/third_party/libjingle/files/talk/base/stringdigest.cc new file mode 100644 index 0000000..1f98124 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/stringdigest.h b/third_party/libjingle/files/talk/base/stringdigest.h new file mode 100644 index 0000000..d75d845 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/base/stringencode.cc b/third_party/libjingle/files/talk/base/stringencode.cc new file mode 100644 index 0000000..ed6edfc --- /dev/null +++ b/third_party/libjingle/files/talk/base/stringencode.cc @@ -0,0 +1,579 @@ +/* + * 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 <malloc.h> +#endif // WIN32 +#ifdef POSIX +#include <alloca.h> +#define _alloca alloca +#endif // POSIX + +#include "talk/base/basictypes.h" +#include "talk/base/common.h" +#include "talk/base/stringencode.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] : '!'; +} + +unsigned char hex_decode(char ch) { + char lower = tolower(ch); + ASSERT(((ch >= '0') && (ch <= '9')) || ((lower >= 'a') && (lower <= 'z'))); + return (ch <= '9') ? (ch - '0') : ((lower - 'a') + 10); +} + +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; + + size_t srcpos = 0, bufpos = 0; + while ((srcpos < srclen) && (bufpos + 1 < buflen)) { + char ch = source[srcpos++]; + if ((ch == escape) && (srcpos + 1 < srclen)) { + buffer[bufpos++] = (hex_decode(source[srcpos]) << 4) + | hex_decode(source[srcpos+1]); + 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 +#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; + + 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)) { + buffer[bufpos++] = (hex_decode(source[srcpos]) << 4) + | hex_decode(source[srcpos+1]); + 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; +} + +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); + + size_t srcpos = 0, bufpos = 0; + while ((srcpos + 1 < srclen) && (bufpos + 1 < buflen)) { + unsigned char v1 = (hex_decode(source[srcpos]) << 4); + unsigned char v2 = hex_decode(source[srcpos+1]); + bbuffer[bufpos++] = v1 | v2; + srcpos += 2; + } + bbuffer[bufpos] = '\0'; + return bufpos; +} + +void transform(std::string& value, size_t maxlen, const std::string& source, + Transform t) { + char * buffer = static_cast<char *>(_alloca(maxlen + 1)); + value.assign(buffer, t(buffer, maxlen + 1, source.data(), source.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 = static_cast<char *>(_alloca(maxlen)); + size_t len = t(buffer, maxlen, source.data(), source.length()); + std::string result(buffer, len); + return result; +} + +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 = static_cast<char *>(alloca(maxlen + 1)); + va_list args; + va_start(args, format); + value.assign(buffer, vsprintfn(buffer, maxlen + 1, format, args)); + va_end(args); +} +*/ + +///////////////////////////////////////////////////////////////////////////// +// Unit Tests +///////////////////////////////////////////////////////////////////////////// + +#ifdef _DEBUG + +static int utf8_unittest() { + const struct Utf8Test { + const char* encoded; + size_t encsize, enclen; + unsigned long decoded; + } kTests[] = { + { "a ", 5, 1, 'a' }, + { "\x7F ", 5, 1, 0x7F }, + { "\xC2\x80 ", 5, 2, 0x80 }, + { "\xDF\xBF ", 5, 2, 0x7FF }, + { "\xE0\xA0\x80 ", 5, 3, 0x800 }, + { "\xEF\xBF\xBF ", 5, 3, 0xFFFF }, + { "\xF0\x90\x80\x80 ", 5, 4, 0x10000 }, + { "\xF0\x90\x80\x80 ", 3, 0, 0x10000 }, + { "\xF0\xF0\x80\x80 ", 5, 0, 0 }, + { "\xF0\x90\x80 ", 5, 0, 0 }, + { "\x90\x80\x80 ", 5, 0, 0 }, + { NULL, 0, 0 }, + }; + for (size_t i=0; kTests[i].encoded; ++i) { + unsigned long val = 0; + ASSERT(kTests[i].enclen == utf8_decode(kTests[i].encoded, + kTests[i].encsize, + &val)); + unsigned long result = (kTests[i].enclen == 0) ? 0 : kTests[i].decoded; + ASSERT(val == result); + + if (kTests[i].decoded == 0) { + // Not an interesting encoding test case + continue; + } + + char buffer[5]; + memset(buffer, 0x01, ARRAY_SIZE(buffer)); + ASSERT(kTests[i].enclen == utf8_encode(buffer, + kTests[i].encsize, + kTests[i].decoded)); + ASSERT(memcmp(buffer, kTests[i].encoded, kTests[i].enclen) == 0); + // Make sure remainder of buffer is unchanged + ASSERT(memory_check(buffer + kTests[i].enclen, + 0x1, + ARRAY_SIZE(buffer) - kTests[i].enclen)); + } + return 1; +} + +int test = utf8_unittest(); + +#endif // _DEBUG + +///////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/stringencode.h b/third_party/libjingle/files/talk/base/stringencode.h new file mode 100644 index 0000000..08e5e4f --- /dev/null +++ b/third_party/libjingle/files/talk/base/stringencode.h @@ -0,0 +1,166 @@ +/* + * 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_STRINGENCODE_H__ +#define TALK_BASE_STRINGENCODE_H__ + +#include <string> +#include <sstream> + +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. +unsigned char hex_decode(char ch); + +// 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); + +// 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); +void 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); +} + +// 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; +} + +// 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/third_party/libjingle/files/talk/base/stringutils.cc b/third_party/libjingle/files/talk/base/stringutils.cc new file mode 100644 index 0000000..1e43637 --- /dev/null +++ b/third_party/libjingle/files/talk/base/stringutils.cc @@ -0,0 +1,84 @@ +/* + * 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; +} + +#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 + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/stringutils.h b/third_party/libjingle/files/talk/base/stringutils.h new file mode 100644 index 0000000..3692368 --- /dev/null +++ b/third_party/libjingle/files/talk/base/stringutils.h @@ -0,0 +1,293 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef TALK_BASE_STRINGUTILS_H__ +#define TALK_BASE_STRINGUTILS_H__ + +#include <ctype.h> +#include <stdarg.h> +#include <stdio.h> +#ifdef WIN32 +#include <wchar.h> +#endif // WIN32 + +#include <string> + +/////////////////////////////////////////////////////////////////////////////// +// Generic string/memory utilities +/////////////////////////////////////////////////////////////////////////////// + +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); + +} // 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 +// Note that the wchar_t versions are not available on Linux +/////////////////////////////////////////////////////////////////////////////// + +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); +} +#if 0 +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 +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); +} + +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; +} + +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; +} + +/////////////////////////////////////////////////////////////////////////////// +// 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* Traits<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 + +} // namespace talk_base + +#endif // TALK_BASE_STRINGUTILS_H__ diff --git a/third_party/libjingle/files/talk/base/tarstream.cc b/third_party/libjingle/files/talk/base/tarstream.cc new file mode 100644 index 0000000..e1f17b1 --- /dev/null +++ b/third_party/libjingle/files/talk/base/tarstream.cc @@ -0,0 +1,601 @@ +#include "talk/base/basicdefs.h" +#include "talk/base/basictypes.h" +#include "talk/base/tarstream.h" +#include "talk/base/pathutils.h" +#include "talk/base/stringutils.h" +#include "talk/base/common.h" + +using namespace talk_base; + +/////////////////////////////////////////////////////////////////////////////// +// TarStream +/////////////////////////////////////////////////////////////////////////////// + +TarStream::TarStream() : mode_(M_NONE), next_block_(NB_NONE), block_pos_(0), + current_(NULL), current_bytes_(0) { +} + +TarStream::~TarStream() { + Close(); +} + +bool TarStream::AddFilter(const std::string& pathname) { + if (pathname.empty()) + return false; + Pathname archive_path(pathname); + archive_path.SetFolderDelimiter('/'); + archive_path.Normalize(); + filters_.push_back(archive_path.pathname()); + return true; +} + +bool TarStream::Open(const std::string& folder, bool read) { + Close(); + + Pathname root_folder; + root_folder.SetFolder(folder); + root_folder.Normalize(); + root_folder_.assign(root_folder.folder()); + + if (read) { + std::string pattern(root_folder_); + DirectoryIterator *iter = new DirectoryIterator(); + + if (iter->Iterate(pattern) == false) { + delete iter; + return false; + } + mode_ = M_READ; + find_.push_front(iter); + next_block_ = NB_FILE_HEADER; + block_pos_ = BLOCK_SIZE; + int error; + if (SR_SUCCESS != ProcessNextEntry(find_.front(), &error)) { + return false; + } + } else { + if (!Filesystem::CreateFolder(root_folder_)) { + return false; + } + + mode_ = M_WRITE; + next_block_ = NB_FILE_HEADER; + block_pos_ = 0; + } + return true; +} + +StreamState TarStream::GetState() const { + return (M_NONE == mode_) ? SS_CLOSED : SS_OPEN; +} + +StreamResult TarStream::Read(void* buffer, size_t buffer_len, + size_t* read, int* error) { + if (M_READ != mode_) { + return SR_EOS; + } + return ProcessBuffer(buffer, buffer_len, read, error); +} + +StreamResult TarStream::Write(const void* data, size_t data_len, + size_t* written, int* error) { + if (M_WRITE != mode_) { + return SR_EOS; + } + // Note: data is not modified unless M_READ == mode_ + return ProcessBuffer(const_cast<void*>(data), data_len, written, error); +} + +void TarStream::Close() { + root_folder_.clear(); + next_block_ = NB_NONE; + block_pos_ = 0; + delete current_; + current_ = NULL; + current_bytes_ = 0; + for (DirectoryList::iterator it = find_.begin(); it != find_.end(); ++it) { + delete(*it); + } + find_.clear(); + subfolder_.clear(); +} + +StreamResult TarStream::ProcessBuffer(void* buffer, size_t buffer_len, + size_t* consumed, int* error) { + size_t local_consumed; + if (!consumed) consumed = &local_consumed; + int local_error; + if (!error) error = &local_error; + + StreamResult result = SR_SUCCESS; + *consumed = 0; + + while (*consumed < buffer_len) { + size_t available = BLOCK_SIZE - block_pos_; + if (available == 0) { + result = ProcessNextBlock(error); + if (SR_SUCCESS != result) { + break; + } + } else { + size_t bytes_to_copy = talk_base::_min(available, buffer_len - *consumed); + char* buffer_ptr = static_cast<char*>(buffer) + *consumed; + char* block_ptr = block_ + block_pos_; + if (M_READ == mode_) { + memcpy(buffer_ptr, block_ptr, bytes_to_copy); + } else { + memcpy(block_ptr, buffer_ptr, bytes_to_copy); + } + *consumed += bytes_to_copy; + block_pos_ += bytes_to_copy; + } + } + + // SR_EOS means no data was consumed on this operation. So we may need to + // return SR_SUCCESS instead, and then we will return SR_EOS next time. + if ((SR_EOS == result) && (*consumed > 0)) { + result = SR_SUCCESS; + } + + return result; +} + +StreamResult TarStream::ProcessNextBlock(int* error) { + ASSERT(NULL != error); + ASSERT(M_NONE != mode_); + ASSERT(BLOCK_SIZE == block_pos_); + + StreamResult result; + if (NB_NONE == next_block_) { + + return SR_EOS; + + } else if (NB_TRAILER == next_block_) { + + // Trailer block is zeroed + result = ProcessEmptyBlock(0, error); + if (SR_SUCCESS != result) + return result; + next_block_ = NB_NONE; + + } else if (NB_FILE_HEADER == next_block_) { + + if (M_READ == mode_) { + result = ReadNextFile(error); + } else { + result = WriteNextFile(error); + } + + // If there are no more files, we are at the first trailer block + if (SR_EOS == result) { + block_pos_ = 0; + next_block_ = NB_TRAILER; + result = ProcessEmptyBlock(0, error); + } + if (SR_SUCCESS != result) + return result; + + } else if (NB_DATA == next_block_) { + + size_t block_consumed = 0; + size_t block_available = talk_base::_min<size_t>(BLOCK_SIZE, current_bytes_); + while (block_consumed < block_available) { + void* block_ptr = static_cast<char*>(block_) + block_consumed; + size_t available = block_available - block_consumed, consumed; + if (M_READ == mode_) { + ASSERT(NULL != current_); + result = current_->Read(block_ptr, available, &consumed, error); + } else if (current_) { + result = current_->Write(block_ptr, available, &consumed, error); + } else { + consumed = available; + result = SR_SUCCESS; + } + switch (result) { + case SR_ERROR: + return result; + case SR_BLOCK: + case SR_EOS: + ASSERT(false); + *error = 0; // TODO: make errors + return SR_ERROR; + case SR_SUCCESS: + block_consumed += consumed; + break; + } + } + + current_bytes_ -= block_consumed; + if (current_bytes_ == 0) { + // The remainder of the block is zeroed + result = ProcessEmptyBlock(block_consumed, error); + if (SR_SUCCESS != result) + return result; + delete current_; + current_ = NULL; + next_block_ = NB_FILE_HEADER; + } + + } else { + ASSERT(false); + } + + block_pos_ = 0; + return SR_SUCCESS; +} + +StreamResult TarStream::ProcessEmptyBlock(size_t start, int* error) { + ASSERT(NULL != error); + ASSERT(M_NONE != mode_); + if (M_READ == mode_) { + memset(block_ + start, 0, BLOCK_SIZE - start); + } else { + if (!talk_base::memory_check(block_ + start, 0, BLOCK_SIZE - start)) { + *error = 0; // TODO: make errors + return SR_ERROR; + } + } + return SR_SUCCESS; +} + +StreamResult TarStream::ReadNextFile(int* error) { + ASSERT(NULL != error); + ASSERT(M_READ == mode_); + ASSERT(NB_FILE_HEADER == next_block_); + ASSERT(BLOCK_SIZE == block_pos_); + ASSERT(NULL == current_); + + // ReadNextFile conducts a depth-first recursive search through the directory + // tree. find_ maintains a stack of open directory handles, which + // corresponds to our current position in the tree. At any point, the + // directory at the top (front) of the stack is being enumerated. If a + // directory is found, it is opened and pushed onto the top of the stack. + // When a directory enumeration completes, that directory is popped off the + // top of the stack. + + // Note: Since ReadNextFile can only return one block of data at a time, we + // cannot simultaneously return the entry for a directory, and the entry for + // the first element in that directory at the same time. In this case, we + // push a NULL entry onto the find_ stack, which indicates that the next + // iteration should begin enumeration of the "new" directory. + StreamResult result = SR_SUCCESS; + while (BLOCK_SIZE == block_pos_) { + ASSERT(!find_.empty()); + + if (NULL != find_.front()) { + if (find_.front()->Next()) { + result = ProcessNextEntry(find_.front(), error); + if (SR_SUCCESS != result) { + return result; + } + continue; + } + delete(find_.front()); + } else { + Pathname pattern(root_folder_); + pattern.AppendFolder(subfolder_); + find_.front() = new DirectoryIterator(); + if (find_.front()->Iterate(pattern.pathname())) { + result = ProcessNextEntry(find_.front(), error); + if (SR_SUCCESS != result) { + return result; + } + continue; + } + // TODO: should this be an error? + LOG_F(LS_WARNING) << "Couldn't open folder: " << pattern.pathname(); + } + + find_.pop_front(); + subfolder_ = Pathname(subfolder_).parent_folder(); + + if (find_.empty()) { + return SR_EOS; + } + } + + ASSERT(0 == block_pos_); + return SR_SUCCESS; +} + +StreamResult TarStream::WriteNextFile(int* error) { + ASSERT(NULL != error); + ASSERT(M_WRITE == mode_); + ASSERT(NB_FILE_HEADER == next_block_); + ASSERT(BLOCK_SIZE == block_pos_); + ASSERT(NULL == current_); + ASSERT(0 == current_bytes_); + + std::string pathname, link, linked_name, magic, mversion; + size_t file_size, modify_time, unused, checksum; + + size_t block_data = 0; + ReadFieldS(block_data, 100, &pathname); + ReadFieldN(block_data, 8, &unused); // mode + ReadFieldN(block_data, 8, &unused); // owner uid + ReadFieldN(block_data, 8, &unused); // owner gid + ReadFieldN(block_data, 12, &file_size); + ReadFieldN(block_data, 12, &modify_time); + ReadFieldN(block_data, 8, &checksum); + if (checksum == 0) + block_data -= 8; // back-compatiblity + ReadFieldS(block_data, 1, &link); + ReadFieldS(block_data, 100, &linked_name); // name of linked file + ReadFieldS(block_data, 6, &magic); + ReadFieldS(block_data, 2, &mversion); + + if (pathname.empty()) + return SR_EOS; + + std::string user, group, dev_major, dev_minor, prefix; + if (magic == "ustar" || magic == "ustar ") { + ReadFieldS(block_data, 32, &user); + ReadFieldS(block_data, 32, &group); + ReadFieldS(block_data, 8, &dev_major); + ReadFieldS(block_data, 8, &dev_minor); + ReadFieldS(block_data, 155, &prefix); + + pathname = prefix + pathname; + } + + // Rest of the block must be empty + StreamResult result = ProcessEmptyBlock(block_data, error); + if (SR_SUCCESS != result) { + return result; + } + + Pathname archive_path(pathname); + archive_path.SetFolderDelimiter('/'); + archive_path.Normalize(); + + bool is_folder = archive_path.filename().empty(); + if (is_folder) { + ASSERT(NB_FILE_HEADER == next_block_); + ASSERT(0 == file_size); + } else if (file_size > 0) { + // We assign current_bytes_ because we must skip over the upcoming data + // segments, regardless of whether we want to write them. + next_block_ = NB_DATA; + current_bytes_ = file_size; + } + + if (!CheckFilter(archive_path.pathname())) { + // If it's a directory, we will ignore it and all children by nature of + // filter prefix matching. If it is a file, we will ignore it because + // current_ is NULL. + return SR_SUCCESS; + } + + // Sanity checks: + // 1) No .. path segments + if (archive_path.pathname().find("../") != std::string::npos) { + LOG_F(LS_WARNING) << "Skipping path with .. entry: " + << archive_path.pathname(); + return SR_SUCCESS; + } + // 2) No drive letters + if (archive_path.pathname().find(':') != std::string::npos) { + LOG_F(LS_WARNING) << "Skipping path with drive letter: " + << archive_path.pathname(); + return SR_SUCCESS; + } + // 3) No absolute paths + if (archive_path.pathname().find("//") != std::string::npos) { + LOG_F(LS_WARNING) << "Skipping absolute path: " + << archive_path.pathname(); + return SR_SUCCESS; + } + + Pathname local_path(root_folder_); + local_path.AppendPathname(archive_path.pathname()); + local_path.Normalize(); + + if (is_folder) { + if (!Filesystem::CreateFolder(local_path)) { + LOG_F(LS_WARNING) << "Couldn't create folder: " << local_path.pathname(); + *error = 0; // TODO + return SR_ERROR; + } + } else { + FileStream* stream = new FileStream; + + if (!stream->Open(local_path.pathname().c_str(), "wb")) { + LOG_F(LS_WARNING) << "Couldn't create file: " << local_path.pathname(); + *error = 0; // TODO + delete stream; + return SR_ERROR; + } + if (file_size > 0) { + current_ = stream; + } else { + stream->Close(); + delete stream; + } + } + + SignalNextEntry(archive_path.filename(), current_bytes_); + + + return SR_SUCCESS; +} + +StreamResult TarStream::ProcessNextEntry(const DirectoryIterator *data, int *error) { + ASSERT(M_READ == mode_); + ASSERT(NB_FILE_HEADER == next_block_); + ASSERT(BLOCK_SIZE == block_pos_); + ASSERT(NULL == current_); + ASSERT(0 == current_bytes_); + + if (data->IsDirectory() && + (data->Name() == "." || data->Name() == "..")) + return SR_SUCCESS; + + Pathname archive_path; + archive_path.SetFolder(subfolder_); + if (data->IsDirectory()) { + archive_path.AppendFolder(data->Name()); + } else { + archive_path.SetFilename(data->Name()); + } + archive_path.SetFolderDelimiter('/'); + archive_path.Normalize(); + + if (!CheckFilter(archive_path.pathname())) + return SR_SUCCESS; + + if (archive_path.pathname().length() > 255) { + // Cannot send a file name longer than 255 (yet) + return SR_ERROR; + } + + Pathname local_path(root_folder_); + local_path.AppendPathname(archive_path.pathname()); + local_path.Normalize(); + + if (data->IsDirectory()) { + // Note: the NULL handle indicates that we need to open the folder next + // time. + find_.push_front(NULL); + subfolder_ = archive_path.pathname(); + } else { + FileStream* stream = new FileStream; + if (!stream->Open(local_path.pathname().c_str(), "rb")) { + // TODO: Should this be an error? + LOG_F(LS_WARNING) << "Couldn't open file: " << local_path.pathname(); + delete stream; + return SR_SUCCESS; + } + current_ = stream; + current_bytes_ = data->FileSize(); + } + + time_t modify_time = data->FileModifyTime(); + + std::string pathname = archive_path.pathname(); + std::string magic, user, group, dev_major, dev_minor, prefix; + std::string name = pathname; + bool ustar = false; + if (name.length() > 100) { + ustar = true; + // Put last 100 characters into the name, and rest in prefix + size_t path_length = pathname.length(); + prefix = pathname.substr(0, path_length - 100); + name = pathname.substr(path_length - 100); + } + + size_t block_data = 0; + memset(block_, 0, BLOCK_SIZE); + WriteFieldS(block_data, 100, name.c_str()); + WriteFieldS(block_data, 8, data->IsDirectory() ? "777" : "666"); // mode + WriteFieldS(block_data, 8, "5"); // owner uid + WriteFieldS(block_data, 8, "5"); // owner gid + WriteFieldN(block_data, 12, current_bytes_); + WriteFieldN(block_data, 12, modify_time); + WriteFieldS(block_data, 8, " "); // Checksum. To be filled in later. + WriteFieldS(block_data, 1, data->IsDirectory() ? "5" : "0"); // link indicator (0 == normal file, 5 == directory) + WriteFieldS(block_data, 100, ""); // name of linked file + + if (ustar) { + WriteFieldS(block_data, 6, "ustar"); + WriteFieldS(block_data, 2, ""); + WriteFieldS(block_data, 32, user.c_str()); + WriteFieldS(block_data, 32, group.c_str()); + WriteFieldS(block_data, 8, dev_major.c_str()); + WriteFieldS(block_data, 8, dev_minor.c_str()); + WriteFieldS(block_data, 155, prefix.c_str()); + } + + // Rest of the block must be empty + StreamResult result = ProcessEmptyBlock(block_data, error); + WriteChecksum(); + + block_pos_ = 0; + if (current_bytes_ > 0) { + next_block_ = data->IsDirectory() ? NB_FILE_HEADER : NB_DATA; + } + + SignalNextEntry(archive_path.filename(), current_bytes_); + + return result; +} + +void TarStream::WriteChecksum() { + unsigned int sum = 0; + + for (int i = 0; i < BLOCK_SIZE; i++) + sum += static_cast<unsigned char>(block_[i]); + + sprintf(block_ + 148, "%06o", sum); +} + +bool TarStream::CheckFilter(const std::string& pathname) { + if (filters_.empty()) + return true; + + // pathname is allowed when there is a filter which: + // A) Equals name + // B) Matches a folder prefix of name + for (size_t i=0; i<filters_.size(); ++i) { + const std::string& filter = filters_[i]; + // Make sure the filter is a prefix of name + if (_strnicmp(pathname.c_str(), filter.data(), filter.length()) != 0) + continue; + + // If the filter is not a directory, must match exactly + if (!Pathname::IsFolderDelimiter(filter[filter.length()-1]) + && (filter.length() != pathname.length())) + continue; + + return true; + } + + return false; +} + +void TarStream::WriteFieldN(size_t& pos, size_t max_len, size_t numeric_field) { + WriteFieldF(pos, max_len, "%.*o", max_len - 1, numeric_field); +} + +void TarStream::WriteFieldS(size_t& pos, size_t max_len, + const char* string_field) { + ASSERT(pos + max_len <= BLOCK_SIZE); + size_t len = strlen(string_field); + size_t use_len = _min(len, max_len); + memcpy(block_ + pos, string_field, use_len); + pos += max_len; +} + +void TarStream::WriteFieldF(size_t& pos, size_t max_len, + const char* format, ...) { + va_list args; + va_start(args, format); + char buffer[BLOCK_SIZE]; + vsprintfn(buffer, ARRAY_SIZE(buffer), format, args); + WriteFieldS(pos, max_len, buffer); + va_end(args); +} + +void TarStream::ReadFieldN(size_t& pos, size_t max_len, size_t* numeric_field) { + ASSERT(NULL != numeric_field); + std::string buffer; + ReadFieldS(pos, max_len, &buffer); + + int value; + if (!buffer.empty() && (1 == sscanf(buffer.c_str(), "%o", &value))) { + *numeric_field = value; + } else { + *numeric_field = 0; + } +} + +void TarStream::ReadFieldS(size_t& pos, size_t max_len, + std::string* string_field) { + ASSERT(NULL != string_field); + ASSERT(pos + max_len <= BLOCK_SIZE); + size_t value_len = talk_base::strlenn(block_ + pos, max_len); + string_field->assign(block_ + pos, value_len); + ASSERT(talk_base::memory_check(block_ + pos + value_len, + 0, + max_len - value_len)); + pos += max_len; +} diff --git a/third_party/libjingle/files/talk/base/tarstream.h b/third_party/libjingle/files/talk/base/tarstream.h new file mode 100644 index 0000000..772fb14 --- /dev/null +++ b/third_party/libjingle/files/talk/base/tarstream.h @@ -0,0 +1,104 @@ +#ifndef TALK_APP_WIN32_TARSTREAM_H__ +#define TALK_APP_WIN32_TARSTREAM_H__ + +#include <string> +#include <vector> +#include "talk/base/fileutils.h" +#include "talk/base/sigslot.h" +#include "talk/base/stream.h" + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// TarStream - acts as a source or sink for a tar-encoded collection of files +// and directories. Operates synchronously. +/////////////////////////////////////////////////////////////////////////////// + +class TarStream : public StreamInterface { + public: + TarStream(); + virtual ~TarStream(); + + // AddFilter is used to limit the elements which will be read or written. + // In general, all members of the parent folder are read, and all members + // of a tarfile are written. However, if any filters are added, only those + // items (and their contents, in the case of folders) are processed. Filters + // must be added before opening the stream. + bool AddFilter(const std::string& pathname); + + // 'folder' is parent of the tar contents. All paths will be evaluated + // relative to it. When 'read' is true, the specified folder will be + // traversed, and a tar stream will be generated (via Read). Otherwise, a + // tar stream is consumed (via Write), and files and folders will be created. + bool Open(const std::string& folder, bool read); + + virtual talk_base::StreamState GetState() const; + virtual talk_base::StreamResult Read(void* buffer, size_t buffer_len, + size_t* read, int* error); + virtual talk_base::StreamResult Write(const void* data, size_t data_len, + size_t* written, int* error); + virtual void Close(); + + virtual bool GetSize(size_t* size) const { return false; } + virtual bool ReserveSize(size_t size) { return true; } + virtual bool Rewind() { return false; } + + // Every time a new entry header is read/written, this signal is fired with + // the entry's name and size. + sigslot::signal2<const std::string&, size_t> SignalNextEntry; + + private: + typedef std::list<DirectoryIterator*> DirectoryList; + enum ModeType { M_NONE, M_READ, M_WRITE }; + enum NextBlockType { NB_NONE, NB_FILE_HEADER, NB_DATA, NB_TRAILER }; + enum { BLOCK_SIZE = 512 }; + + talk_base::StreamResult ProcessBuffer(void* buffer, size_t buffer_len, + size_t* consumed, int* error); + talk_base::StreamResult ProcessNextBlock(int* error); + talk_base::StreamResult ProcessEmptyBlock(size_t start, int* error); + talk_base::StreamResult ReadNextFile(int* error); + talk_base::StreamResult WriteNextFile(int* error); + + talk_base::StreamResult ProcessNextEntry(const DirectoryIterator *data, + int *error); + + // Determine whether the given entry is allowed by our filters + bool CheckFilter(const std::string& pathname); + + void WriteFieldN(size_t& pos, size_t max_len, size_t numeric_field); + void WriteFieldS(size_t& pos, size_t max_len, const char* string_field); + void WriteFieldF(size_t& pos, size_t max_len, const char* format, ...); + + void ReadFieldN(size_t& pos, size_t max_len, size_t* numeric_field); + void ReadFieldS(size_t& pos, size_t max_len, std::string* string_field); + + void WriteChecksum(void); + + // Files and/or folders that should be processed + std::vector<std::string> filters_; + // Folder passed to Open + std::string root_folder_; + // Open for read or write? + ModeType mode_; + // The expected type of the next block + NextBlockType next_block_; + // The partial contents of the current block + char block_[BLOCK_SIZE]; + size_t block_pos_; + // The file which is currently being read or written + talk_base::FileStream* current_; + // Bytes remaining to be processed for current_ + size_t current_bytes_; + // Note: the following variables are used in M_READ mode only. + // Stack of open directory handles, representing depth-first search + DirectoryList find_; + // Subfolder path corresponding to current position in the directory tree + std::string subfolder_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_APP_WIN32_TARSTREAM_H__ diff --git a/third_party/libjingle/files/talk/base/task.cc b/third_party/libjingle/files/talk/base/task.cc new file mode 100644 index 0000000..70059da --- /dev/null +++ b/third_party/libjingle/files/talk/base/task.cc @@ -0,0 +1,299 @@ +/* + * 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/task.h" +#include "talk/base/common.h" +#include "talk/base/taskrunner.h" + +namespace talk_base { + +int32 Task::unique_id_seed_ = 0; + +Task::Task(Task *parent) + : state_(STATE_INIT), + parent_(parent), + blocked_(false), + done_(false), + aborted_(false), + busy_(false), + error_(false), + child_error_(false), + start_time_(0), + timeout_seconds_(0), + timeout_time_(0), + timeout_suspended_(false) { + children_.reset(new ChildSet()); + runner_ = ((parent == NULL) ? + reinterpret_cast<TaskRunner *>(this) : + parent->GetRunner()); + if (parent_ != NULL) { + parent_->AddChild(this); + } + + unique_id_ = unique_id_seed_++; + + // sanity check that we didn't roll-over our id seed + ASSERT(unique_id_ < unique_id_seed_); +} + +int64 Task::CurrentTime() { + return runner_->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(); + 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(); + blocked_ = true; + } +} + +void Task::Abort(bool nowake) { + if (done_) + return; + aborted_ = true; + if (!busy_) { + done_ = true; + blocked_ = true; + error_ = true; + Stop(); + if (!nowake) + 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::AddChild(Task *child) { + children_->insert(child); +} + +bool Task::AllChildrenDone() { + for (ChildSet::iterator it = children_->begin(); + it != children_->end(); + ++it) { + if (!(*it)->IsDone()) + return false; + } + return true; +} + +bool Task::AnyChildError() { + return child_error_; +} + +void Task::AbortAllChildren() { + if (children_->size() > 0) { + ChildSet copy = *children_; + for (ChildSet::iterator it = copy.begin(); it != copy.end(); ++it) { + (*it)->Abort(true); // Note we do not wake + } + } +} + +void Task::Stop() { + // No need to wake because we're either awake or in abort + AbortAllChildren(); + parent_->OnChildStopped(this); +} + +void Task::OnChildStopped(Task *child) { + if (child->HasError()) + child_error_ = true; + children_->erase(child); +} + +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() { + 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); +} + +void Task::ClearTimeout() { + timeout_time_ = 0; + GetRunner()->UpdateTaskTimeout(this); +} + +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/third_party/libjingle/files/talk/base/task.h b/third_party/libjingle/files/talk/base/task.h new file mode 100644 index 0000000..b524ab7 --- /dev/null +++ b/third_party/libjingle/files/talk/base/task.h @@ -0,0 +1,218 @@ +/* + * 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/scoped_ptr.h" +#include "talk/base/basictypes.h" +#include "talk/base/sigslot.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 sometimea 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 infomration, 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 { + +class TaskRunner; + +// A task executes a sequence of steps + +class Task; +class RootTask; + +class Task { + public: + Task(Task *parent); + virtual ~Task() {} + + int32 get_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(); + + Task *GetParent() { return parent_; } + TaskRunner *GetRunner() { return runner_; } + virtual Task *GetParent(int code) { return parent_->GetParent(code); } + + // Called from outside to stop task without any more callbacks + void Abort(bool nowake = false); + + // For managing children + bool AllChildrenDone(); + bool AnyChildError(); + + bool TimedOut(); + + int64 get_timeout_time() { return timeout_time_; } + 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; } + + // for managing children (if any) + void AddChild(Task *child); + void AbortAllChildren(); + + 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(); + void OnChildStopped(Task *child); + + int state_; + Task *parent_; + TaskRunner *runner_; + bool blocked_; + bool done_; + bool aborted_; + bool busy_; + bool error_; + bool child_error_; + int64 start_time_; + int64 timeout_time_; + int timeout_seconds_; + bool timeout_suspended_; + int32 unique_id_; + + static int32 unique_id_seed_; + + // for managing children + typedef std::set<Task *> ChildSet; + scoped_ptr<ChildSet> children_; +}; + +} // namespace talk_base + +#endif // TALK_BASE_TASK_H__ diff --git a/third_party/libjingle/files/talk/base/taskrunner.cc b/third_party/libjingle/files/talk/base/taskrunner.cc new file mode 100644 index 0000000..050171f --- /dev/null +++ b/third_party/libjingle/files/talk/base/taskrunner.cc @@ -0,0 +1,176 @@ +/* + * 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() + : Task(NULL), + tasks_running_(false), + next_timeout_task_(NULL) { +} + +TaskRunner::~TaskRunner() { + // this kills and deletes children silently! + AbortAllChildren(); + RunTasks(); +} + +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); + + WakeTasks(); +} + +void TaskRunner::RunTasks() { + // Running continues until all tasks are Blocked (ok for a small # of tasks) + if (tasks_running_) { + return; // don't reenter + } + + tasks_running_ = true; + + 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->get_unique_id() == next_timeout_task_->get_unique_id()) { + next_timeout_task_ = NULL; + need_timeout_recalc = true; + } + + delete task; + 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); + + 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 + if (next_timeout_task_ && + next_timeout_task_->TimedOut()) { + next_timeout_task_->Wake(); + WakeTasks(); + } +} + +// 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) { + ASSERT(task != NULL); + + // if the relevant task has a timeout, then + // check to see if it's closer than the current + // "about to timeout" task + if (task->get_timeout_time()) { + if (next_timeout_task_ == NULL || + (task->get_timeout_time() <= + next_timeout_task_->get_timeout_time())) { + next_timeout_task_ = task; + } + } else if (next_timeout_task_ != NULL && + task->get_unique_id() == next_timeout_task_->get_unique_id()) { + // 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); + } +} + +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->get_timeout_time() > 0)) + // if it doesn't match our "exclude" task + if (exclude_task == NULL || + exclude_task->get_unique_id() != task->get_unique_id()) + // if its timeout time is sooner than our current timeout time + if (next_timeout_time == 0 || + task->get_timeout_time() <= next_timeout_time) { + // set this task as our next-to-timeout + next_timeout_time = task->get_timeout_time(); + next_timeout_task_ = task; + } + } +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/taskrunner.h b/third_party/libjingle/files/talk/base/taskrunner.h new file mode 100644 index 0000000..e1c5ed6 --- /dev/null +++ b/third_party/libjingle/files/talk/base/taskrunner.h @@ -0,0 +1,74 @@ +/* + * 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/sigslot.h" +#include "talk/base/task.h" + +namespace talk_base { + +class Task; + +const int64 kSecToMsec = 1000; +const int64 kMsecTo100ns = 10000; +const int64 kSecTo100ns = kSecToMsec * kMsecTo100ns; + +class TaskRunner : public Task, public sigslot::has_slots<> { + public: + TaskRunner(); + virtual ~TaskRunner(); + + virtual void WakeTasks() = 0; + + // This method returns the current time in 100ns units since 1/1/1601. This + // Is the GetSystemTimeAsFileTime method on windows. + virtual int64 CurrentTime() = 0 ; + + void StartTask(Task *task); + void RunTasks(); + void PollTasks(); + + void UpdateTaskTimeout(Task *task); + + // dummy state machine - never run. + virtual int ProcessStart() { return STATE_DONE; } + + private: + std::vector<Task *> tasks_; + Task *next_timeout_task_; + bool tasks_running_; + + void RecalcNextTimeout(Task *exclude_task); +}; + +} // namespace talk_base + +#endif // TASK_BASE_TASKRUNNER_H__ diff --git a/third_party/libjingle/files/talk/base/testclient.cc b/third_party/libjingle/files/talk/base/testclient.cc new file mode 100644 index 0000000..ecb45fc --- /dev/null +++ b/third_party/libjingle/files/talk/base/testclient.cc @@ -0,0 +1,161 @@ +/* + * 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 <cassert> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +#include "talk/base/testclient.h" +#include "talk/base/time.h" + + +namespace talk_base { + +// DESIGN: Each packet received is posted to ourselves as a message on the +// thread given to the constructor. When we receive the message, we +// put it into a list of packets. We take the latter step so that we +// can wait for a new packet to arrive by calling Get/Dispatch on the +// thread. + +TestClient::TestClient(AsyncPacketSocket* socket, Thread* thread) + : thread_(thread), socket_(socket) { + if (!thread_) + thread_ = Thread::Current(); + packets_ = new std::vector<Packet*>(); + socket_->SignalReadPacket.connect(this, &TestClient::OnPacket); +} + +TestClient::~TestClient() { + delete socket_; + for (unsigned i = 0; i < packets_->size(); i++) + delete (*packets_)[i]; +} + +void TestClient::Send(const char* buf, size_t size) { + int result = socket_->Send(buf, size); + if (result < 0) { + std::cerr << "send: " << std::strerror(errno) << std::endl; + exit(1); + } +} + +void TestClient::SendTo( + const char* buf, size_t size, const SocketAddress& dest) { + int result = socket_->SendTo(buf, size, dest); + if (result < 0) { + std::cerr << "sendto: " << std::strerror(errno) << std::endl; + exit(1); + } +} + +void TestClient::OnPacket( + const char* buf, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* socket) { + thread_->Post(this, 0, new Packet(remote_addr, buf, size)); +} + +void TestClient::OnMessage(Message *pmsg) { + assert(pmsg->pdata); + packets_->push_back(new Packet(*static_cast<Packet*>(pmsg->pdata))); +} + +TestClient::Packet* TestClient::NextPacket() { + + // If no packets are currently available, we go into a get/dispatch loop for + // at most 1 second. If, during the loop, a packet arrives, then we can stop + // early and return it. + // + // Note that the case where no packet arrives is important. We often want to + // test that a packet does not arrive. + + if (packets_->size() == 0) { + uint32 msNext = 1000; + uint32 msEnd = GetMillisecondCount() + msNext; + + while (true) { + Message msg; + if (!thread_->Get(&msg, msNext)) + break; + thread_->Dispatch(&msg); + + uint32 msCur = GetMillisecondCount(); + if (msCur >= msEnd) + break; + msNext = msEnd - msCur; + + if (packets_->size() > 0) + break; + } + } + + if (packets_->size() == 0) { + return 0; + } else { + // Return the first packet placed in the queue. + Packet* packet = packets_->front(); + packets_->erase(packets_->begin()); + return packet; + } +} + +void TestClient::CheckNextPacket( + const char* buf, size_t size, SocketAddress* addr) { + Packet* packet = NextPacket(); + assert(packet); + assert(packet->size == size); + assert(std::memcmp(packet->buf, buf, size) == 0); + if (addr) + *addr = packet->addr; +} + +void TestClient::CheckNoPacket() { + Packet* packet = NextPacket(); + assert(!packet); +} + +TestClient::Packet::Packet(const SocketAddress& a, const char* b, size_t s) + : addr(a), buf(0), size(s) { + buf = new char[size]; + memcpy(buf, b, size); +} + +TestClient::Packet::Packet(const Packet& p) + : addr(p.addr), buf(0), size(p.size) { + buf = new char[size]; + memcpy(buf, p.buf, size); +} + +TestClient::Packet::~Packet() { + delete buf; +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/testclient.h b/third_party/libjingle/files/talk/base/testclient.h new file mode 100644 index 0000000..012e8583 --- /dev/null +++ b/third_party/libjingle/files/talk/base/testclient.h @@ -0,0 +1,89 @@ +/* + * 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_TESTCLIENT_H__ +#define TALK_BASE_TESTCLIENT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/thread.h" +#include <vector> + +namespace talk_base { + +class TestClient : public MessageHandler, public sigslot::has_slots<> { +public: + // Creates a client that will send and receive with the given socket and + // will post itself messages with the given thread. + TestClient(AsyncPacketSocket* socket, Thread* thread = 0); + ~TestClient(); + + // Sends using the clients socket. + void Send(const char* buf, size_t size); + + // Sends using the clients socket to the given destination. + void SendTo(const char* buf, size_t size, const SocketAddress& dest); + + // Records the contents of a packet that was received. + struct Packet : public MessageData { + Packet(const SocketAddress& a, const char* b, size_t s); + Packet(const Packet& p); + virtual ~Packet(); + + SocketAddress addr; + char* buf; + size_t size; + }; + + // Returns the next packet received by the client or 0 if none is received + // within a reasonable amount of time. The caller must delete the packet + // when done with it. + Packet* NextPacket(); + + // Checks that the next packet has the given contents. Returns the remote + // address that the packet was sent from. + void CheckNextPacket(const char* buf, size_t len, SocketAddress* addr); + + // Checks that no packets have arrived or will arrive in the next second. + void CheckNoPacket(); + + // Messagehandler: + void OnMessage(Message *pmsg); + +private: + Thread* thread_; + AsyncPacketSocket* socket_; + std::vector<Packet*>* packets_; + + // Slot for packets read on the socket. + void OnPacket( + const char* buf, size_t len, const SocketAddress& remote_addr, + AsyncPacketSocket* socket); +}; + +} // namespace talk_base + +#endif // TALK_BASE_TESTCLIENT_H__ diff --git a/third_party/libjingle/files/talk/base/thread.cc b/third_party/libjingle/files/talk/base/thread.cc new file mode 100644 index 0000000..8fc45e1 --- /dev/null +++ b/third_party/libjingle/files/talk/base/thread.cc @@ -0,0 +1,353 @@ +/* + * 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 +extern "C" { +#include <sys/time.h> +} +#endif + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/thread.h" +#include "talk/base/time.h" + +#define MSDEV_SET_THREAD_NAME 0x406D1388 + +namespace talk_base { + +ThreadManager g_thmgr; + +#ifdef POSIX +pthread_key_t ThreadManager::key_; + +ThreadManager::ThreadManager() { + pthread_key_create(&key_, NULL); + main_thread_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + pthread_key_delete(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (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_ = new Thread(); + SetCurrent(main_thread_); +} + +ThreadManager::~ThreadManager() { + TlsFree(key_); + delete main_thread_; +} + +Thread *ThreadManager::CurrentThread() { + return (Thread *)TlsGetValue(key_); +} + +void ThreadManager::SetCurrent(Thread *thread) { + TlsSetValue(key_, thread); +} +#endif + +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()); +} + +Thread::Thread(SocketServer* ss) : MessageQueue(ss), priority_(PRIORITY_NORMAL) { + g_thmgr.Add(this); + started_ = false; + has_sends_ = false; +} + +Thread::~Thread() { + Stop(); + if (active_) + Clear(NULL); + g_thmgr.Remove(this); +} + +#ifdef POSIX +void Thread::Start() { + pthread_attr_t attr; + pthread_attr_init(&attr); + if (priority_ == PRIORITY_IDLE) { + struct sched_param param; + pthread_attr_getschedparam(&attr, ¶m); + param.sched_priority = 15; // +15 = + pthread_attr_setschedparam(&attr, ¶m); + } + pthread_create(&thread_, &attr, PreRun, this); + started_ = true; +} + +void Thread::Join() { + if (started_) { + void *pv; + pthread_join(thread_, &pv); + } +} +#endif + +#ifdef WIN32 + +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), (DWORD*)&info ); + } + __except(EXCEPTION_CONTINUE_EXECUTION) + { + } +} + +void Thread::Start() { + DWORD flags = 0; + if (priority_ != PRIORITY_NORMAL) { + flags = CREATE_SUSPENDED; + } + thread_ = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PreRun, this, flags, NULL); + if (thread_) { + if (priority_ != PRIORITY_NORMAL) { + if (priority_ == PRIORITY_IDLE) { + ::SetThreadPriority(thread_, THREAD_PRIORITY_IDLE); + } + ::ResumeThread(thread_); + } + } + started_ = true; +} + +void Thread::Join() { + if (started_) { + WaitForSingleObject(thread_, INFINITE); + CloseHandle(thread_); + started_ = false; + } +} +#endif + +void *Thread::PreRun(void *pv) { + Thread *thread = (Thread *)pv; + ThreadManager::SetCurrent(thread); +#if defined(WIN32) && defined(_DEBUG) + char buf[256]; + _snprintf(buf, sizeof(buf), "Thread 0x%.8x", thread); + SetThreadName(GetCurrentThreadId(), buf); +#endif + thread->Run(); + return NULL; +} + +void Thread::Run() { + ProcessMessages(kForever); +} + +void Thread::Stop() { + MessageQueue::Stop(); + 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) { + 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 (phandler == NULL || smsg.msg.phandler == phandler) { + if (id == (uint32)-1 || smsg.msg.message_id == id) { + iter = sendlist_.erase(iter); + *smsg.ready = true; + smsg.thread->socketserver()->WakeUp(); + continue; + } + } + ++iter; + } + + MessageQueue::Clear(phandler, id); +} + +bool Thread::ProcessMessages(int cmsLoop) { + uint32 msEnd; + if (cmsLoop != kForever) + msEnd = GetMillisecondCount() + cmsLoop; + int cmsNext = cmsLoop; + + while (true) { + Message msg; + if (!Get(&msg, cmsNext)) + return false; + Dispatch(&msg); + + if (cmsLoop != kForever) { + uint32 msCur = GetMillisecondCount(); + if (msCur >= msEnd) + return true; + cmsNext = msEnd - msCur; + } + } +} + +AutoThread::AutoThread(SocketServer* ss) : Thread(ss) { + if (!ThreadManager::CurrentThread()) { + ThreadManager::SetCurrent(this); + } +} + +AutoThread::~AutoThread() { + if (ThreadManager::CurrentThread() == this) { + ThreadManager::SetCurrent(NULL); + } +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/thread.h b/third_party/libjingle/files/talk/base/thread.h new file mode 100644 index 0000000..5da0aed --- /dev/null +++ b/third_party/libjingle/files/talk/base/thread.h @@ -0,0 +1,161 @@ +/* + * 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 <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); + +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_NORMAL, + PRIORITY_IDLE, +}; + +class Thread : public MessageQueue { +public: + Thread(SocketServer* ss = 0); + virtual ~Thread(); + + static inline Thread* Current() { + return ThreadManager::CurrentThread(); + } + inline bool IsCurrent() const { + return (ThreadManager::CurrentThread() == this); + } + + void SetPriority(ThreadPriority priority) { + priority_ = priority; + } + + virtual void Start(); + 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 = (uint32)-1); + 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); + +#ifdef WIN32 + HANDLE GetHandle() { + return thread_; + } +#endif + +private: + static void *PreRun(void *pv); + void Join(); + + std::list<_SendMessage> sendlist_; + ThreadPriority priority_; + bool started_; + bool has_sends_; + +#ifdef POSIX + pthread_t thread_; +#endif + +#ifdef WIN32 + HANDLE thread_; +#endif + + 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(); +}; + +} // namespace talk_base + +#endif // TALK_BASE_THREAD_H__ diff --git a/third_party/libjingle/files/talk/base/time.cc b/third_party/libjingle/files/talk/base/time.cc new file mode 100644 index 0000000..d4a61ac --- /dev/null +++ b/third_party/libjingle/files/talk/base/time.cc @@ -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. + */ + +#include <iostream> +#include <cstdlib> +#include <cstring> + +#include "talk/base/time.h" + +namespace talk_base { + +#ifdef POSIX +#include <sys/time.h> +uint32 Time() { + struct timeval tv; + gettimeofday(&tv, 0); + return tv.tv_sec * 1000 + tv.tv_usec / 1000; +} +#endif + +#ifdef WIN32 +#include <windows.h> +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 ElapsedTime() { + return TimeDiff(Time(), StartTime()); +} + +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier) { + if (earlier <= later) { + return ((earlier <= middle) && (middle <= later)); + } else { + return !((later < middle) && (middle < earlier)); + } +} + +int32 TimeDiff(uint32 later, uint32 earlier) { + uint32 LAST = 0xFFFFFFFF; + uint32 HALF = 0x80000000; + if (TimeIsBetween(earlier + HALF, later, earlier)) { + 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); + } + } +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/time.h b/third_party/libjingle/files/talk/base/time.h new file mode 100644 index 0000000..9088aae --- /dev/null +++ b/third_party/libjingle/files/talk/base/time.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 TALK_BASE_TIME_H__ +#define TALK_BASE_TIME_H__ + +#include "talk/base/basictypes.h" + +namespace talk_base { + +// Returns the current time in milliseconds. +uint32 Time(); + +// Approximate time when the program started. +uint32 StartTime(); + +// Elapsed milliseconds since StartTime() +uint32 ElapsedTime(); + +// TODO: Delete this old version. +#define GetMillisecondCount Time + +// Comparisons between time values, which can wrap around. +bool TimeIsBetween(uint32 later, uint32 middle, uint32 earlier); +int32 TimeDiff(uint32 later, uint32 earlier); + +} // namespace talk_base + +#endif // TALK_BASE_TIME_H__ diff --git a/third_party/libjingle/files/talk/base/unixfilesystem.cc b/third_party/libjingle/files/talk/base/unixfilesystem.cc new file mode 100644 index 0000000..2c2732f --- /dev/null +++ b/third_party/libjingle/files/talk/base/unixfilesystem.cc @@ -0,0 +1,223 @@ +/* + * 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 <errno.h> +#include <cassert> + +#include "talk/base/basicdefs.h" +#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" + +namespace talk_base { + +bool UnixFilesystem::CreateFolderI(const Pathname &path) { + LOG(LS_INFO) << "Creating folder: " << path.pathname(); + int len = path.pathname().length(); + const char *pathname = path.pathname().c_str(); + if ((len <= 0) || (pathname[len-1] != '/')) + return false; + struct stat st; + int res = ::stat(pathname, &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] !='/')); + + char *newstring = new char[len+1]; + strncpy(newstring, pathname, len); + newstring[len] = '\0'; + + if (!CreateFolder(Pathname(newstring))) { + delete[] newstring; + return false; + } + delete[] newstring; + std::string no_slash(path.pathname(), 0, path.pathname().length()-1); + + return (::mkdir(no_slash.c_str(), 0755) == 0); + } + +FileStream *UnixFilesystem::OpenFileI(const Pathname &filename, + const std::string &mode) { + talk_base::FileStream *fs = new talk_base::FileStream(); + if (fs) + fs->Open(filename.pathname().c_str(), mode.c_str()); + return fs; +} + +bool UnixFilesystem::DeleteFileI(const Pathname &filename) { + LOG(LS_INFO) << "Deleting " << filename.pathname(); + + if (IsFolder(filename)) { + Pathname dir; + dir.SetFolder(filename.pathname()); + DirectoryIterator di; + di.Iterate(dir.pathname()); + while(di.Next()) { + if (di.Name() == "." || di.Name() == "..") + continue; + Pathname subdir; + subdir.SetFolder(filename.pathname()); + subdir.SetFilename(di.Name()); + + if (!DeleteFile(subdir.pathname())) + return false; + } + std::string no_slash(filename.pathname(), 0, filename.pathname().length()-1); + return ::rmdir(no_slash.c_str()) == 0; + } + return ::unlink(filename.pathname().c_str()) == 0; +} + +bool UnixFilesystem::GetTemporaryFolderI(Pathname &pathname, bool create, + const std::string *append) { + pathname.SetPathname("/tmp"); + if (append) { + pathname.AppendFolder(*append); + if (create) + CreateFolder(pathname); + } +} + +std::string UnixFilesystem::TempFilenameI(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::MoveFileI(const Pathname &old_path, const Pathname &new_path) +{ + LOG(LS_INFO) << "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::IsFolderI(const Pathname &path) +{ + struct stat st; + if (stat(path.pathname().c_str(), &st) < 0) + return false; + + return S_ISDIR(st.st_mode); +} + +bool UnixFilesystem::CopyFileI(const Pathname &old_path, const Pathname &new_path) +{ + LOG(LS_INFO) << "Copying " << old_path.pathname() << " to " << new_path.pathname(); + char buf[256]; + size_t len; + if (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 (!CopyFile(source, dest)) + return false; + } + return true; + } + + 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) == talk_base::SR_SUCCESS) + dest->Write(buf, len, NULL, NULL); + + delete source; + delete dest; + return true; +} + +bool UnixFilesystem::IsTemporaryPathI(const Pathname& pathname) +{ + return (!strncmp(pathname.pathname().c_str(), "/tmp/", strlen("/tmp/"))); +} + +bool UnixFilesystem::FileExistsI(const Pathname& pathname) +{ + struct stat st; + int res = ::stat(pathname.pathname().c_str(), &st); + return res == 0; +} + +bool UnixFilesystem::GetFileSizeI(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; +} + +} diff --git a/third_party/libjingle/files/talk/base/unixfilesystem.h b/third_party/libjingle/files/talk/base/unixfilesystem.h new file mode 100644 index 0000000..bd145bb --- /dev/null +++ b/third_party/libjingle/files/talk/base/unixfilesystem.h @@ -0,0 +1,86 @@ +/* + * 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 Filesystem{ + public: + + virtual bool CreateFolderI(const Pathname &pathname); + + // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise, + // returns NULL. + virtual FileStream *OpenFileI(const Pathname &filename, + const std::string &mode); + + // This will attempt to delete the path located at filename. If filename is a file, + // it will be unlinked. If the path is a directory, it will recursively unlink and remove + // all the files and directory within it + virtual bool DeleteFileI(const Pathname &filename); + + // Creates a directory. This will call itself recursively to create /foo/bar even if + // /foo does not exist. + // Returns TRUE if function succeeds + + // 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 MoveFileI(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 CopyFileI(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolderI(const Pathname& pathname); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPathI(const Pathname& pathname); + + // Returns true of pathname represents an existing file + virtual bool FileExistsI(const Pathname& pathname); + + virtual std::string TempFilenameI(const Pathname &dir, const std::string &prefix); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolderI(Pathname &path, bool create, + const std::string *append); + + virtual bool GetFileSizeI(const Pathname &path, size_t *size); + + }; + +} + +#endif // _UNIXFILESYSTEM_H__ diff --git a/third_party/libjingle/files/talk/base/urlencode.cc b/third_party/libjingle/files/talk/base/urlencode.cc new file mode 100644 index 0000000..b654c35 --- /dev/null +++ b/third_party/libjingle/files/talk/base/urlencode.cc @@ -0,0 +1,120 @@ +#include "talk/base/urlencode.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 UrlDecode(const char *source, char *dest) +{ + char * start = dest; + + while (*source) { + switch (*source) { + case '+': + *(dest++) = ' '; + 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 UrlEncode(const char *source, char *dest, unsigned max) +{ + static const char *digits = "0123456789ABCDEF"; + unsigned char ch; + unsigned len = 0; + char *start = dest; + + while (len < max - 4 && *source) + { + ch = (unsigned char)*source; + if (*source == ' ') { + *dest++ = '+'; + } + else if (isalnum(ch) || strchr("-_.!~*'()", ch)) { + *dest++ = *source; + } + else { + *dest++ = '%'; + *dest++ = digits[(ch >> 4) & 0x0F]; + *dest++ = digits[ ch & 0x0F]; + } + source++; + } + *dest = 0; + return start - dest; +} + +std::string +UrlDecodeString(const std::string & encoded) { + const char * sz_encoded = encoded.c_str(); + size_t needed_length = encoded.length(); + for (const char * pch = sz_encoded; *pch; pch++) { + if (*pch == '%') + needed_length += 2; + } + needed_length += 10; + char stackalloc[64]; + char * buf = needed_length > sizeof(stackalloc)/sizeof(*stackalloc) ? + (char *)malloc(needed_length) : stackalloc; + UrlDecode(encoded.c_str(), buf); + std::string result(buf); + if (buf != stackalloc) { + free(buf); + } + return result; +} + +std::string +UrlEncodeString(const std::string & decoded) { + const char * sz_decoded = decoded.c_str(); + size_t needed_length = decoded.length() * 3 + 3; + char stackalloc[64]; + char * buf = needed_length > sizeof(stackalloc)/sizeof(*stackalloc) ? + (char *)malloc(needed_length) : stackalloc; + UrlEncode(decoded.c_str(), buf, needed_length); + std::string result(buf); + if (buf != stackalloc) { + free(buf); + } + return result; +} diff --git a/third_party/libjingle/files/talk/base/urlencode.h b/third_party/libjingle/files/talk/base/urlencode.h new file mode 100644 index 0000000..9d2b3f6 --- /dev/null +++ b/third_party/libjingle/files/talk/base/urlencode.h @@ -0,0 +1,12 @@ +#ifndef _URLENCODE_H_ +#define _URLENCODE_H_ + +#include <string> + +int UrlDecode(const char *source, char *dest); +int UrlEncode(const char *source, char *dest, unsigned max); +std::string UrlDecodeString(const std::string & encoded); +std::string UrlEncodeString(const std::string & decoded); + +#endif + diff --git a/third_party/libjingle/files/talk/base/virtualsocket_unittest.cc b/third_party/libjingle/files/talk/base/virtualsocket_unittest.cc new file mode 100644 index 0000000..16456a2 --- /dev/null +++ b/third_party/libjingle/files/talk/base/virtualsocket_unittest.cc @@ -0,0 +1,239 @@ +// Copyright 2006, Google Inc. + +#include <complex> +#include <iostream> +#include <cassert> + +#include "talk/base/thread.h" +#include "talk/base/virtualsocketserver.h" +#include "talk/base/testclient.h" +#include "talk/base/time.h" + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +using namespace talk_base; + +void test_basic(Thread* thread, VirtualSocketServer* ss) { + std::cout << "basic: "; + std::cout.flush(); + + SocketAddress addr1(ss->GetNextIP(), 5000); + AsyncUDPSocket* socket = CreateAsyncUDPSocket(ss); + socket->Bind(addr1); + + TestClient* client1 = new TestClient(socket); + TestClient* client2 = new TestClient(CreateAsyncUDPSocket(ss)); + + SocketAddress addr2; + client2->SendTo("foo", 3, addr1); + client1->CheckNextPacket("foo", 3, &addr2); + + SocketAddress addr3; + client1->SendTo("bizbaz", 6, addr2); + client2->CheckNextPacket("bizbaz", 6, &addr3); + assert(addr3 == addr1); + + for (int i = 0; i < 10; i++) { + client2 = new TestClient(CreateAsyncUDPSocket(ss)); + + SocketAddress addr4; + client2->SendTo("foo", 3, addr1); + client1->CheckNextPacket("foo", 3, &addr4); + assert((addr4.ip() == addr2.ip()) && (addr4.port() == addr2.port() + 1)); + + SocketAddress addr5; + client1->SendTo("bizbaz", 6, addr4); + client2->CheckNextPacket("bizbaz", 6, &addr5); + assert(addr5 == addr1); + + addr2 = addr4; + } + + std::cout << "PASS" << std::endl; +} + +// Sends at a constant rate but with random packet sizes. +struct Sender : public MessageHandler { + Sender(Thread* th, AsyncUDPSocket* s, uint32 rt) + : thread(th), socket(s), done(false), rate(rt), count(0) { + last_send = GetMillisecondCount(); + thread->PostDelayed(NextDelay(), this, 1); + } + + uint32 NextDelay() { + uint32 size = (rand() % 4096) + 1; + return 1000 * size / rate; + } + + void OnMessage(Message* pmsg) { + assert(pmsg->message_id == 1); + + if (done) + return; + + uint32 cur_time = GetMillisecondCount(); + uint32 delay = cur_time - last_send; + uint32 size = rate * delay / 1000; + size = std::min(size, uint32(4096)); + size = std::max(size, uint32(4)); + + count += size; + *reinterpret_cast<uint32*>(dummy) = cur_time; + socket->Send(dummy, size); + + last_send = cur_time; + thread->PostDelayed(NextDelay(), this, 1); + } + + Thread* thread; + AsyncUDPSocket* socket; + bool done; + uint32 rate; // bytes per second + uint32 count; + uint32 last_send; + char dummy[4096]; +}; + +struct Receiver : public MessageHandler, public sigslot::has_slots<> { + Receiver(Thread* th, AsyncUDPSocket* s, uint32 bw) + : thread(th), socket(s), bandwidth(bw), done(false), count(0), + sec_count(0), sum(0), sum_sq(0), samples(0) { + socket->SignalReadPacket.connect(this, &Receiver::OnReadPacket); + thread->PostDelayed(1000, this, 1); + } + + ~Receiver() { + thread->Clear(this); + } + + void OnReadPacket( + const char* data, size_t size, const SocketAddress& remote_addr, + AsyncPacketSocket* s) { + assert(s == socket); + assert(size >= 4); + + count += size; + sec_count += size; + + uint32 send_time = *reinterpret_cast<const uint32*>(data); + uint32 recv_time = GetMillisecondCount(); + uint32 delay = recv_time - send_time; + sum += delay; + sum_sq += delay * delay; + samples += 1; + } + + void OnMessage(Message* pmsg) { + assert(pmsg->message_id == 1); + // It is always possible for us to receive more than expected because + // packets can be further delayed in delivery. + if (bandwidth > 0) + assert(sec_count <= 5 * bandwidth / 4); + sec_count = 0; + thread->PostDelayed(1000, this, 1); + } + + Thread* thread; + AsyncUDPSocket* socket; + uint32 bandwidth; + bool done; + uint32 count; + uint32 sec_count; + double sum; + double sum_sq; + uint32 samples; +}; + +void test_bandwidth(Thread* thread, VirtualSocketServer* ss) { + std::cout << "bandwidth: "; + std::cout.flush(); + + AsyncUDPSocket* send_socket = CreateAsyncUDPSocket(ss); + AsyncUDPSocket* recv_socket = CreateAsyncUDPSocket(ss); + assert(send_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0); + assert(recv_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0); + assert(send_socket->Connect(recv_socket->GetLocalAddress()) >= 0); + + uint32 bandwidth = 64 * 1024; + ss->set_bandwidth(bandwidth); + + Sender sender(thread, send_socket, 80 * 1024); + Receiver receiver(thread, recv_socket, bandwidth); + + Thread* pthMain = Thread::Current(); + pthMain->ProcessMessages(5000); + sender.done = true; + pthMain->ProcessMessages(5000); + + assert(receiver.count >= 5 * 3 * bandwidth / 4); + assert(receiver.count <= 6 * bandwidth); // queue could drain for 1 sec + + delete send_socket; + delete recv_socket; + + ss->set_bandwidth(0); + + std::cout << "PASS" << std::endl; +} + +void test_delay(Thread* thread, VirtualSocketServer* ss) { + std::cout << "delay: "; + std::cout.flush(); + + uint32 mean = 2000; + uint32 stddev = 500; + + ss->set_delay_mean(mean); + ss->set_delay_stddev(stddev); + ss->UpdateDelayDistribution(); + + AsyncUDPSocket* send_socket = CreateAsyncUDPSocket(ss); + AsyncUDPSocket* recv_socket = CreateAsyncUDPSocket(ss); + assert(send_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0); + assert(recv_socket->Bind(SocketAddress(ss->GetNextIP(), 1000)) >= 0); + assert(send_socket->Connect(recv_socket->GetLocalAddress()) >= 0); + + Sender sender(thread, send_socket, 64 * 1024); + Receiver receiver(thread, recv_socket, 0); + + Thread* pthMain = Thread::Current(); + pthMain->ProcessMessages(5000); + sender.done = true; + pthMain->ProcessMessages(5000); + + double sample_mean = receiver.sum / receiver.samples; + double num = receiver.sum_sq - 2 * sample_mean * receiver.sum + + receiver.samples * sample_mean * sample_mean; + double sample_stddev = std::sqrt(num / (receiver.samples - 1)); +std::cout << "mean=" << sample_mean << " dev=" << sample_stddev << std::endl; + + assert(0.9 * mean <= sample_mean); + assert(sample_mean <= 1.1 * mean); + assert(0.9 * stddev <= sample_stddev); + assert(sample_stddev <= 1.1 * stddev); + + delete send_socket; + delete recv_socket; + + ss->set_delay_mean(0); + ss->set_delay_stddev(0); + ss->UpdateDelayDistribution(); + + std::cout << "PASS" << std::endl; +} + +int main(int argc, char* argv) { + Thread *pthMain = Thread::Current(); + VirtualSocketServer* ss = new VirtualSocketServer(); + pthMain->set_socketserver(ss); + + test_basic(pthMain, ss); + test_bandwidth(pthMain, ss); + test_delay(pthMain, ss); + + return 0; +} diff --git a/third_party/libjingle/files/talk/base/virtualsocketserver.cc b/third_party/libjingle/files/talk/base/virtualsocketserver.cc new file mode 100644 index 0000000..884b324 --- /dev/null +++ b/third_party/libjingle/files/talk/base/virtualsocketserver.cc @@ -0,0 +1,616 @@ +/* + * libjingle + * Copyright 2004--2005, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include <algorithm> +#include <cassert> +#include <cmath> +#include <cstring> +#include <iostream> +#include <vector> +#include <errno.h> + +#include "talk/base/virtualsocketserver.h" +#include "talk/base/common.h" +#include "talk/base/time.h" + +namespace talk_base { + +const uint32 HEADER_SIZE = 28; // IP + UDP headers + +const uint32 MSG_ID_PACKET = 1; +// TODO: Add a message type for new connections. + +// Packets are passed between sockets as messages. We copy the data just like +// the kernel does. +class Packet : public MessageData { +public: + Packet(const char* data, size_t size, const SocketAddress& from) + : size_(size), from_(from) { + assert(data); + assert(size_ >= 0); + data_ = new char[size_]; + std::memcpy(data_, data, size_); + } + + virtual ~Packet() { + delete data_; + } + + const char* data() const { return data_; } + size_t size() const { return size_; } + const SocketAddress& from() const { return from_; } + + // Remove the first size bytes from the data. + void Consume(size_t size) { + assert(size < size_); + size_ -= size; + char* new_data = new char[size_]; + std::memcpy(new_data, data_, size); + delete[] data_; + data_ = new_data; + } + +private: + char* data_; + size_t size_; + SocketAddress from_; +}; + +// Implements the socket interface using the virtual network. Packets are +// passed as messages using the message queue of the socket server. +class VirtualSocket : public AsyncSocket, public MessageHandler { +public: + VirtualSocket( + VirtualSocketServer* server, int type, bool async, uint32 ip) + : server_(server), type_(type), async_(async), connected_(false), + local_ip_(ip), readable_(true), queue_size_(0) { + assert((type_ == SOCK_DGRAM) || (type_ == SOCK_STREAM)); + packets_ = new std::vector<Packet*>(); + } + + ~VirtualSocket() { + Close(); + + for (unsigned i = 0; i < packets_->size(); i++) + delete (*packets_)[i]; + delete packets_; + } + + SocketAddress GetLocalAddress() const { + return local_addr_; + } + + SocketAddress GetRemoteAddress() const { + return remote_addr_; + } + + int Bind(const SocketAddress& addr) { + assert(addr.port() != 0); + int result = server_->Bind(addr, this); + if (result >= 0) + local_addr_ = addr; + else + error_ = EADDRINUSE; + return result; + } + + int Connect(const SocketAddress& addr) { + assert(!connected_); + connected_ = true; + remote_addr_ = addr; + assert(type_ == SOCK_DGRAM); // stream not yet implemented + return 0; + } + + int Close() { + if (!local_addr_.IsAny()) + server_->Unbind(local_addr_, this); + + connected_ = false; + local_addr_ = SocketAddress(); + remote_addr_ = SocketAddress(); + return 0; + } + + int Send(const void *pv, size_t cb) { + assert(connected_); + return SendInternal(pv, cb, remote_addr_); + } + + int SendTo(const void *pv, size_t cb, const SocketAddress& addr) { + assert(!connected_); + return SendInternal(pv, cb, addr); + } + + int SendInternal(const void *pv, size_t cb, const SocketAddress& addr) { + // If we have not been assigned a local port, then get one. + if (local_addr_.IsAny()) { + local_addr_.SetIP(local_ip_); + int result = server_->Bind(this, &local_addr_); + if (result < 0) { + local_addr_.SetIP(0); + error_ = EADDRINUSE; + return result; + } + } + + // Send the data in a message to the appropriate socket. + return server_->Send(this, pv, cb, local_addr_, addr); + } + + int Recv(void *pv, size_t cb) { + SocketAddress addr; + return RecvFrom(pv, cb, &addr); + } + + int RecvFrom(void *pv, size_t cb, SocketAddress *paddr) { + // If we don't have a packet, then either error or wait for one to arrive. + if (packets_->size() == 0) { + if (async_) { + error_ = EAGAIN; + return -1; + } + while (packets_->size() == 0) { + Message msg; + server_->msg_queue_->Get(&msg); + server_->msg_queue_->Dispatch(&msg); + } + } + + // Return the packet at the front of the queue. + Packet* packet = packets_->front(); + *paddr = packet->from(); + int size = (int)packet->size(); + if (size <= (int)cb) { + std::memcpy(pv, packet->data(), size); + packets_->erase(packets_->begin()); + delete packet; + return size; + } else { + std::memcpy(pv, packet->data(), cb); + packet->Consume(cb); + return (int)cb; + } + } + + int Listen(int backlog) { + assert(false); // not yet implemented + return 0; + } + + Socket* Accept(SocketAddress *paddr) { + assert(false); // not yet implemented + return 0; + } + + bool readable() { return readable_; } + void set_readable(bool value) { readable_ = value; } + + bool writable() { return false; } + void set_writable(bool value) { + // TODO: Send ourselves messages (delayed after the first) to give them a + // chance to write. + assert(false); + } + + int GetError() const { + return error_; + } + + void SetError(int error) { + error_ = error; + } + + ConnState GetState() const { + return connected_ ? CS_CONNECTED : CS_CLOSED; + } + + int SetOption(Option opt, int value) { + return 0; + } + + int EstimateMTU(uint16* mtu) { + if (!connected_) + return ENOTCONN; + else + return 65536; + } + + void OnMessage(Message *pmsg) { + if (pmsg->message_id == MSG_ID_PACKET) { + assert(pmsg->pdata); + Packet* packet = static_cast<Packet*>(pmsg->pdata); + + if (!readable_) + return; + + packets_->push_back(packet); + + if (async_) { + SignalReadEvent(this); + + // TODO: If the listeners don't want to read this packet now, we will + // need to send ourselves delayed messages to try again. + assert(packets_->size() == 0); + } + } else { + assert(false); + } + } + +private: + struct QueueEntry { + uint32 size; + uint32 done_time; + }; + + typedef std::deque<QueueEntry> SendQueue; + + VirtualSocketServer* server_; + int type_; + bool async_; + bool connected_; + uint32 local_ip_; + bool readable_; + SocketAddress local_addr_; + SocketAddress remote_addr_; + std::vector<Packet*>* packets_; + int error_; + SendQueue queue_; + uint32 queue_size_; + CriticalSection queue_crit_; + + friend class VirtualSocketServer; +}; + +VirtualSocketServer::VirtualSocketServer() + : fWait_(false), wait_version_(0), next_ip_(1), next_port_(45000), + bandwidth_(0), queue_capacity_(64 * 1024), delay_mean_(0), + delay_stddev_(0), delay_dist_(0), drop_prob_(0.0) { + msg_queue_ = new MessageQueue(); // uses physical socket server for Wait + bindings_ = new AddressMap(); + + UpdateDelayDistribution(); +} + +VirtualSocketServer::~VirtualSocketServer() { + delete bindings_; + delete msg_queue_; + delete delay_dist_; +} + +uint32 VirtualSocketServer::GetNextIP() { + return next_ip_++; +} + +Socket* VirtualSocketServer::CreateSocket(int type) { + return CreateSocketInternal(type); +} + +AsyncSocket* VirtualSocketServer::CreateAsyncSocket(int type) { + return CreateSocketInternal(type); +} + +VirtualSocket* VirtualSocketServer::CreateSocketInternal(int type) { + uint32 ip = (next_ip_ > 1) ? next_ip_ - 1 : 1; + return new VirtualSocket(this, type, true, ip); +} + +bool VirtualSocketServer::Wait(int cmsWait, bool process_io) { + ASSERT(process_io); // This can't be easily supported. + + uint32 msEnd; + if (cmsWait != kForever) + msEnd = GetMillisecondCount() + cmsWait; + uint32 cmsNext = cmsWait; + + fWait_ = true; + wait_version_ += 1; + + while (fWait_) { + Message msg; + if (!msg_queue_->Get(&msg, cmsNext)) + return true; + msg_queue_->Dispatch(&msg); + + if (cmsWait != kForever) { + uint32 msCur = GetMillisecondCount(); + if (msCur >= msEnd) + return true; + cmsNext = msEnd - msCur; + } + } + return true; +} + +const uint32 MSG_WAKE_UP = 1; + +struct WakeUpMessage : public MessageData { + WakeUpMessage(uint32 ver) : wait_version(ver) {} + virtual ~WakeUpMessage() {} + + uint32 wait_version; +}; + +void VirtualSocketServer::WakeUp() { + msg_queue_->Post(this, MSG_WAKE_UP, new WakeUpMessage(wait_version_)); +} + +void VirtualSocketServer::OnMessage(Message* pmsg) { + assert(pmsg->message_id == MSG_WAKE_UP); + assert(pmsg->pdata); + WakeUpMessage* wmsg = static_cast<WakeUpMessage*>(pmsg->pdata); + if (wmsg->wait_version == wait_version_) + fWait_ = false; + delete pmsg->pdata; +} + +int VirtualSocketServer::Bind( + const SocketAddress& addr, VirtualSocket* socket) { + assert(addr.ip() > 0); // don't support any-address right now + assert(addr.port() > 0); + assert(socket); + + if (bindings_->find(addr) == bindings_->end()) { + (*bindings_)[addr] = socket; + return 0; + } else { + return -1; + } +} + +int VirtualSocketServer::Bind(VirtualSocket* socket, SocketAddress* addr) { + assert(addr->ip() > 0); // don't support any-address right now + assert(socket); + + for (int i = 0; i < 65536; i++) { + addr->SetPort(next_port_++); + if (addr->port() > 0) { + AddressMap::iterator iter = bindings_->find(*addr); + if (iter == bindings_->end()) { + (*bindings_)[*addr] = socket; + return 0; + } + } + } + + errno = EADDRINUSE; // TODO: is there a better error number? + return -1; +} + +int VirtualSocketServer::Unbind( + const SocketAddress& addr, VirtualSocket* socket) { + assert((*bindings_)[addr] == socket); + bindings_->erase(bindings_->find(addr)); + return 0; +} + +static double Random() { + return double(rand()) / RAND_MAX; +} + +int VirtualSocketServer::Send( + VirtualSocket* socket, const void *pv, size_t cb, + const SocketAddress& local_addr, const SocketAddress& remote_addr) { + + // See if we want to drop this packet. + if (Random() < drop_prob_) { + std::cerr << "Dropping packet: bad luck" << std::endl; + return 0; + } + + uint32 cur_time = GetMillisecondCount(); + uint32 send_delay; + + // Determine whether we have enough bandwidth to accept this packet. To do + // this, we need to update the send queue. Once we know it's current size, + // we know whether we can fit this packet. + // + // NOTE: There are better algorithms for maintaining such a queue (such as + // "Derivative Random Drop"); however, this algorithm is a more accurate + // simulation of what a normal network would do. + { + CritScope cs(&socket->queue_crit_); + + while ((socket->queue_.size() > 0) && + (socket->queue_.front().done_time <= cur_time)) { + assert(socket->queue_size_ >= socket->queue_.front().size); + socket->queue_size_ -= socket->queue_.front().size; + socket->queue_.pop_front(); + } + + VirtualSocket::QueueEntry entry; + entry.size = uint32(cb) + HEADER_SIZE; + + if (socket->queue_size_ + entry.size > queue_capacity_) { + std::cerr << "Dropping packet: queue capacity exceeded" << std::endl; + return 0; // not an error + } + + socket->queue_size_ += entry.size; + send_delay = SendDelay(socket->queue_size_); + entry.done_time = cur_time + send_delay; + socket->queue_.push_back(entry); + } + + // Find the delay for crossing the many virtual hops of the network. + uint32 transit_delay = GetRandomTransitDelay(); + + // Post the packet as a message to be delivered (on our own thread) + + AddressMap::iterator iter = bindings_->find(remote_addr); + if (iter != bindings_->end()) { + Packet* p = new Packet(static_cast<const char*>(pv), cb, local_addr); + uint32 delay = send_delay + transit_delay; + msg_queue_->PostDelayed(delay, iter->second, MSG_ID_PACKET, p); + } else { + std::cerr << "No one listening at " << remote_addr.ToString() << std::endl; + } + return (int)cb; +} + +uint32 VirtualSocketServer::SendDelay(uint32 size) { + if (bandwidth_ == 0) + return 0; + else + return 1000 * size / bandwidth_; +} + +void PrintFunction(std::vector<std::pair<double,double> >* f) { + for (uint32 i = 0; i < f->size(); i++) + std::cout << (*f)[i].first << '\t' << (*f)[i].second << std::endl; +} + +void VirtualSocketServer::UpdateDelayDistribution() { + Function* dist = GetDelayDistribution(); + dist = Resample(Invert(Accumulate(dist)), 0, 1); + + // We take a lock just to make sure we don't leak memory. + { + CritScope cs(&delay_crit_); + delete delay_dist_; + delay_dist_ = dist; + } +} + +const int NUM_SAMPLES = 100; // 1000; + +static double PI = 4 * std::atan(1.0); + +static double Normal(double x, double mean, double stddev) { + double a = (x - mean) * (x - mean) / (2 * stddev * stddev); + return std::exp(-a) / (stddev * sqrt(2 * PI)); +} + +#if 0 // static unused gives a warning +static double Pareto(double x, double min, double k) { + if (x < min) + return 0; + else + return k * std::pow(min, k) / std::pow(x, k+1); +} +#endif + +VirtualSocketServer::Function* VirtualSocketServer::GetDelayDistribution() { + Function* f = new Function(); + + if (delay_stddev_ == 0) { + + f->push_back(Point(delay_mean_, 1.0)); + + } else { + + double start = 0; + if (delay_mean_ >= 4 * double(delay_stddev_)) + start = delay_mean_ - 4 * double(delay_stddev_); + double end = delay_mean_ + 4 * double(delay_stddev_); + + double delay_min = 0; + if (delay_mean_ >= 1.0 * delay_stddev_) + delay_min = delay_mean_ - 1.0 * delay_stddev_; + + for (int i = 0; i < NUM_SAMPLES; i++) { + double x = start + (end - start) * i / (NUM_SAMPLES - 1); + double y = Normal(x, delay_mean_, delay_stddev_); + f->push_back(Point(x, y)); + } + + } + + return f; +} + +uint32 VirtualSocketServer::GetRandomTransitDelay() { + double delay = (*delay_dist_)[rand() % delay_dist_->size()].second; + return uint32(delay); +} + +struct FunctionDomainCmp { + bool operator ()(const VirtualSocketServer::Point& p1, const VirtualSocketServer::Point& p2) { + return p1.first < p2.first; + } + bool operator ()(double v1, const VirtualSocketServer::Point& p2) { + return v1 < p2.first; + } + bool operator ()(const VirtualSocketServer::Point& p1, double v2) { + return p1.first < v2; + } +}; + +VirtualSocketServer::Function* VirtualSocketServer::Accumulate(Function* f) { + assert(f->size() >= 1); + double v = 0; + for (Function::size_type i = 0; i < f->size() - 1; ++i) { + double dx = (*f)[i].second * ((*f)[i+1].first - (*f)[i].first); + v = (*f)[i].second = v + dx; + } + (*f)[f->size()-1].second = v; + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Invert(Function* f) { + for (Function::size_type i = 0; i < f->size(); ++i) + std::swap((*f)[i].first, (*f)[i].second); + + std::sort(f->begin(), f->end(), FunctionDomainCmp()); + return f; +} + +VirtualSocketServer::Function* VirtualSocketServer::Resample( + Function* f, double x1, double x2) { + Function* g = new Function(); + + for (int i = 0; i < NUM_SAMPLES; i++) { + double x = x1 + (x2 - x1) * i / (NUM_SAMPLES - 1); + double y = Evaluate(f, x); + g->push_back(Point(x, y)); + } + + delete f; + return g; +} + +double VirtualSocketServer::Evaluate(Function* f, double x) { + Function::iterator iter = + std::lower_bound(f->begin(), f->end(), x, FunctionDomainCmp()); + if (iter == f->begin()) { + return (*f)[0].second; + } else if (iter == f->end()) { + assert(f->size() >= 1); + return (*f)[f->size() - 1].second; + } else if (iter->first == x) { + return iter->second; + } else { + double x1 = (iter - 1)->first; + double y1 = (iter - 1)->second; + double x2 = iter->first; + double y2 = iter->second; + return y1 + (y2 - y1) * (x - x1) / (x2 - x1); + } +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/virtualsocketserver.h b/third_party/libjingle/files/talk/base/virtualsocketserver.h new file mode 100644 index 0000000..d2e894a --- /dev/null +++ b/third_party/libjingle/files/talk/base/virtualsocketserver.h @@ -0,0 +1,156 @@ +/* + * 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_VIRTUALSOCKETSERVER_H__ +#define TALK_BASE_VIRTUALSOCKETSERVER_H__ + +#include <cassert> +#include <deque> +#include <map> + +#include "talk/base/messagequeue.h" +#include "talk/base/socketserver.h" + +namespace talk_base { + +class VirtualSocket; + +// Simulates a network in the same manner as a loopback interface. The +// interface can create as many addresses as you want. All of the sockets +// created by this network will be able to communicate with one another. +class VirtualSocketServer : public SocketServer, public MessageHandler { +public: + VirtualSocketServer(); + virtual ~VirtualSocketServer(); + + // Returns a new IP not used before in this network. + uint32 GetNextIP(); + + // Limits the network bandwidth (maximum bytes per second). Zero means that + // all sends occur instantly. + uint32 bandwidth() { return bandwidth_; } + void set_bandwidth(uint32 bandwidth) { bandwidth_ = bandwidth; } + + // Limits the total size of packets that will be kept in the send queue, + // waiting for their turn to be written to the network Defaults to 64 KB. + uint32 queue_capacity() { return queue_capacity_; } + void set_queue_capacity(uint32 queue_capacity) { + queue_capacity_ = queue_capacity; + } + + // Controls the (transit) delay for packets sent in the network. This does + // not inclue the time required to sit in the send queue. Both of these + // values are measured in milliseconds. + uint32 delay_mean() { return delay_mean_; } + uint32 delay_stddev() { return delay_stddev_; } + void set_delay_mean(uint32 delay_mean) { delay_mean_ = delay_mean; } + void set_delay_stddev(uint32 delay_stddev) { + delay_stddev_ = delay_stddev; + } + + // If the (transit) delay parameters are modified, this method should be + // called to recompute the new distribution. + void UpdateDelayDistribution(); + + // Controls the (uniform) probability that any sent packet is dropped. This + // is separate from calculations to drop based on queue size. + double drop_probability() { return drop_prob_; } + void set_drop_probability(double drop_prob) { + assert((0 <= drop_prob) && (drop_prob <= 1)); + drop_prob_ = drop_prob; + } + + // SocketFactory: + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + + // SocketServer: + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + // Used to send internal wake-up messages. + virtual void OnMessage(Message* msg); + + typedef std::pair<double,double> Point; + typedef std::vector<Point> Function; + +private: + friend class VirtualSocket; + + typedef std::map<SocketAddress, VirtualSocket*> AddressMap; + + MessageQueue* msg_queue_; + bool fWait_; + uint32 wait_version_; + uint32 next_ip_; + uint16 next_port_; + AddressMap* bindings_; + + uint32 bandwidth_; + uint32 queue_capacity_; + uint32 delay_mean_; + uint32 delay_stddev_; + Function* delay_dist_; + CriticalSection delay_crit_; + + double drop_prob_; + + VirtualSocket* CreateSocketInternal(int type); + + // Attempts to bind the given socket to the given (non-zero) address. + int Bind(const SocketAddress& addr, VirtualSocket* socket); + + // Binds the given socket to the given (non-zero) IP on an unused port. + int Bind(VirtualSocket* socket, SocketAddress* addr); + + // Removes the binding for the given socket. + int Unbind(const SocketAddress& addr, VirtualSocket* socket); + + // Sends the given packet to the socket at the given address (if one exists). + int Send(VirtualSocket* socket, const void *pv, size_t cb, + const SocketAddress& local_addr, const SocketAddress& remote_addr); + + // Computes the number of milliseconds required to send a packet of this size. + uint32 SendDelay(uint32 size); + + // Returns the probability density function for the transit delay. + Function* GetDelayDistribution(); + + // Returns a random transit delay chosen from the appropriate distribution. + uint32 GetRandomTransitDelay(); + + // Basic operations on functions. Those that return a function also take + // ownership of the function given (and hence, may modify or delete it). + Function* Accumulate(Function* f); + Function* Invert(Function* f); + Function* Resample(Function* f, double x1, double x2); + double Evaluate(Function* f, double x); +}; + +} // namespace talk_base + +#endif // TALK_BASE_VIRTUALSOCKETSERVER_H__ diff --git a/third_party/libjingle/files/talk/base/win32.h b/third_party/libjingle/files/talk/base/win32.h new file mode 100644 index 0000000..082e84f --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32.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 TALK_BASE_WIN32_H__ +#define TALK_BASE_WIN32_H__ + +#include <winsock2.h> +#include <windows.h> +#include <malloc.h> + +#include <string> + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// + +inline std::wstring ToUtf16(const std::string& str) { + int len16 = ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), + NULL, 0); + wchar_t *ws = static_cast<wchar_t*>(_alloca(len16 * sizeof(wchar_t))); + ::MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), ws, len16); + std::wstring result(ws, len16); + return result; +} + +inline std::string ToUtf8(const std::wstring& wstr) { + int len8 = ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr.length(), + NULL, 0, NULL, NULL); + char* ns = static_cast<char*>(_alloca(len8)); + ::WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr.length(), + ns, len8, NULL, NULL); + std::string result(ns, len8); + return result; +} + +// 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); + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_WIN32_H__ diff --git a/third_party/libjingle/files/talk/base/win32filesystem.cc b/third_party/libjingle/files/talk/base/win32filesystem.cc new file mode 100644 index 0000000..5b8b023 --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32filesystem.cc @@ -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. + */ +#include <errno.h> +#include <cassert> + +#include "talk/base/basicdefs.h" +#include "talk/base/convert.h" +#include "talk/base/pathutils.h" +#include "talk/base/fileutils.h" +#include "talk/base/stringutils.h" +#include "talk/base/stream.h" + +#include "talk/base/win32filesystem.h" + +namespace talk_base { + +bool Win32Filesystem::CreateFolderI(const Pathname &pathname) { + int len = pathname.pathname().length(); + + if ((len <= 0) || (pathname.pathname().c_str()[len-1] != '\\')) { + return false; + } + + DWORD res = ::GetFileAttributes(Utf16(pathname.pathname()).AsWz()); + 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 + do { + --len; + } while ((len > 0) && (pathname.pathname().c_str()[len-1] != '\\')); + + if (!CreateFolder(std::string(pathname.pathname().c_str(),len))) + return false; + + if (pathname.pathname().c_str()[0] != '\\') { + std::string long_path = std::string("\\\\?\\") + pathname.pathname(); + return (::CreateDirectory(Utf16(long_path).AsWz(), NULL) != 0); + } else { + return (::CreateDirectory(Utf16(pathname.pathname()).AsWz(), NULL) != 0); + } +} + +FileStream *Win32Filesystem::OpenFileI(const Pathname &filename, + const std::string &mode) { + talk_base::FileStream *fs = new talk_base::FileStream(); + if (fs) + fs->Open(filename.pathname().c_str(), mode.c_str()); + return fs; +} + +bool Win32Filesystem::DeleteFileI(const Pathname &filename) { + LOG(LS_INFO) << "Deleting " << filename.pathname(); + + if (IsFolder(filename)) { + Pathname dir; + dir.SetFolder(filename.pathname()); + DirectoryIterator di; + di.Iterate(dir.pathname()); + while(di.Next()) { + if (di.Name() == "." || di.Name() == "..") + continue; + Pathname subdir; + subdir.SetFolder(filename.pathname()); + subdir.SetFilename(di.Name()); + + if (!DeleteFile(subdir.pathname())) + return false; + } + std::string no_slash(filename.pathname(), 0, filename.pathname().length()-1); + return ::RemoveDirectory(Utf16(no_slash).AsWz()) == 0; + } + return ::DeleteFile(Utf16(filename.pathname()).AsWz()) == 0; +} + +bool Win32Filesystem::GetTemporaryFolderI(Pathname &pathname, bool create, + const std::string *append) { + ASSERT(!g_application_name_.empty()); + wchar_t buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + size_t len = strlen(buffer); + if ((len > 0) && (buffer[len-1] != '\\')) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + L"\\"); + } + if ((len > 0) && (buffer[len-1] != '\\')) { + len += talk_base::strcpyn(buffer + len, ARRAY_SIZE(buffer) - len, + L"\\"); + } + if (len >= ARRAY_SIZE(buffer) - 1) + return false; + pathname.clear(); + pathname.SetFolder(Utf8(buffer).AsSz()); + if (append != NULL) + pathname.AppendFolder(*append); + if (create) + CreateFolderI(pathname); + return true; +} + +std::string Win32Filesystem::TempFilenameI(const Pathname &dir, const std::string &prefix) { + wchar_t filename[MAX_PATH]; + if (::GetTempFileName(Utf16(dir.pathname()).AsWz(), Utf16(prefix).AsWz(), 0, filename) == 0) + return Utf8(filename).AsString(); + return ""; +} + +bool Win32Filesystem::MoveFileI(const Pathname &old_path, const Pathname &new_path) +{ + LOG(LS_INFO) << "Moving " << old_path.pathname() << " to " << new_path.pathname(); + if (_wrename(Utf16(old_path.pathname()).AsWz(), Utf16(new_path.pathname()).AsWz()) != 0) { + if (errno != EXDEV) { + printf("errno: %d\n", errno); + return false; + } + if (!CopyFile(old_path, new_path)) + return false; + if (!DeleteFile(old_path)) + return false; + } + return true; +} + +bool Win32Filesystem::IsFolderI(const Pathname &path) +{ + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (0 == ::GetFileAttributesEx(Utf16(path.pathname()).AsWz(), GetFileExInfoStandard, &data)) + return false; + return (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; +} + +bool Win32Filesystem::FileExistsI(const Pathname &path) +{ + DWORD res = ::GetFileAttributes(Utf16(path.pathname()).AsWz()); + return res != INVALID_FILE_ATTRIBUTES; +} + +bool Win32Filesystem::CopyFileI(const Pathname &old_path, const Pathname &new_path) +{ + return ::CopyFile(Utf16(old_path.pathname()).AsWz(), Utf16(new_path.pathname()).AsWz(), TRUE) == 0; +} + +bool Win32Filesystem::IsTemporaryPathI(const Pathname& pathname) +{ + TCHAR buffer[MAX_PATH + 1]; + if (!::GetTempPath(ARRAY_SIZE(buffer), buffer)) + return false; + if (!::GetLongPathName(buffer, buffer, ARRAY_SIZE(buffer))) + return false; + return (::strnicmp(Utf16(pathname.pathname()).AsWz(), buffer, strlen(buffer)) == 0); +} + +bool Win32Filesystem::GetFileSizeI(const Pathname &pathname, size_t *size) +{ + WIN32_FILE_ATTRIBUTE_DATA data = {0}; + if (::GetFileAttributesEx(Utf16(pathname.pathname()).AsWz(), GetFileExInfoStandard, &data) == 0) + return false; + *size = data.nFileSizeLow; + return true; +} + +} diff --git a/third_party/libjingle/files/talk/base/win32filesystem.h b/third_party/libjingle/files/talk/base/win32filesystem.h new file mode 100644 index 0000000..1c13157 --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32filesystem.h @@ -0,0 +1,91 @@ +/* + * 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 Filesystem{ + public: + + virtual bool CreateFolderI(const Pathname &pathname); + + // Opens a file. Returns an open StreamInterface if function succeeds. Otherwise, + // returns NULL. + virtual FileStream *OpenFileI(const Pathname &filename, + const std::string &mode); + + // This will attempt to delete the path located at filename. If filename is a file, + // it will be unlinked. If the path is a directory, it will recursively unlink and remove + // all the files and directory within it + virtual bool DeleteFileI(const Pathname &filename); + + // Creates a directory. This will call itself recursively to create /foo/bar even if + // /foo does not exist. + // Returns TRUE if function succeeds + + // 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 MoveFileI(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 CopyFileI(const Pathname &old_path, const Pathname &new_path); + + // Returns true if a pathname is a directory + virtual bool IsFolderI(const Pathname& pathname); + + // Returns true if a file exists at path + virtual bool FileExistsI(const Pathname &path); + + // Returns true if pathname represents a temporary location on the system. + virtual bool IsTemporaryPathI(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 TempFilenameI(const Pathname &dir, const std::string &prefix); + + virtual bool GetFileSizeI(const Pathname &pathname, size_t *size); + + // A folder appropriate for storing temporary files (Contents are + // automatically deleted when the program exists) + virtual bool GetTemporaryFolderI(Pathname &path, bool create, + const std::string *append); + }; + +} + +#endif // _WIN32FILESYSTEM_H__ diff --git a/third_party/libjingle/files/talk/base/win32socketserver.cc b/third_party/libjingle/files/talk/base/win32socketserver.cc new file mode 100644 index 0000000..49c8fee --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32socketserver.cc @@ -0,0 +1,767 @@ +/* + * 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/logging.h" +#include "talk/base/winping.h" +#include "talk/base/win32socketserver.h" +#include "talk/base/win32window.h" +#include <ws2tcpip.h> + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +static const int kfRead = 0x0001; +static const int kfWrite = 0x0002; + +// 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; + +#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 sockaddr_in& addr) { + talk_base::SocketAddress address; + address.FromSockAddr(addr); + 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 sockaddr_in& addr) { } +#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: + 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; + + // Make sure the socket isn't already closed + if (!parent_ || (parent_->socket_ == INVALID_SOCKET)) + return true; + + int event = WSAGETSELECTEVENT(lParam); + int wsa_error = WSAGETSELECTERROR(lParam); + + if (uMsg == WM_TIMER) { + event = FD_CLOSE; + wsa_error = WSAETIMEDOUT; + } else if (event == FD_CLOSE) { + char ch; + if (::recv(parent_->socket_, &ch, 1, MSG_PEEK) > 0) { + parent_->signal_close_ = true; + return true; + } + } + + parent_->OnSocketNotify(event, wsa_error); + return true; +} + +bool +Win32Socket::EventSink::OnDnsNotify(WPARAM wParam, LPARAM lParam, + LRESULT& result) { + result = 0; + + if (!parent_) + return true; + + if (!parent_->dns_ || + (parent_->dns_->handle != reinterpret_cast<HANDLE>(wParam))) { + ASSERT(false); + return true; + } + + uint32 ip = 0; + int error = WSAGETASYNCERROR(lParam); + + if (error == 0) { + hostent * pHost = reinterpret_cast<hostent *>(parent_->dns_->buffer); + uint32 net_ip = *reinterpret_cast<uint32 *>(pHost->h_addr_list[0]); + ip = talk_base::NetworkToHost32(net_ip); + } + + parent_->OnDnsNotify(ip, error); + return true; +} + +void +Win32Socket::EventSink::OnFinalMessage(HWND hWnd) { + delete this; +} + +///////////////////////////////////////////////////////////////////////////// +// Win32Socket +///////////////////////////////////////////////////////////////////////////// + +Win32Socket::Win32Socket() + : socket_(INVALID_SOCKET), error_(0), state_(CS_CLOSED), + signal_close_(false), sink_(NULL), dns_(NULL) { + // TODO: replace addr_ with SocketAddress + memset(&addr_, 0, sizeof(addr_)); +} + +Win32Socket::~Win32Socket() { + Close(); +} + +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 (!Create(FD_READ | FD_WRITE | FD_CLOSE)) + return SOCKET_ERROR; + + return 0; +} + +void +Win32Socket::SetTimeout(int ms) { + if (sink_) + ::SetTimer(sink_->handle(), 1, ms, 0); +} + +talk_base::SocketAddress +Win32Socket::GetLocalAddress() const { + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getsockname(socket_, (sockaddr*)&addr, &addrlen); + ASSERT(addrlen == sizeof(addr)); + talk_base::SocketAddress address; + if (result >= 0) { + address.FromSockAddr(addr); + } else { + ASSERT(result >= 0); + } + return address; +} + +talk_base::SocketAddress +Win32Socket::GetRemoteAddress() const { + sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + int result = ::getpeername(socket_, (sockaddr*)&addr, &addrlen); + ASSERT(addrlen == sizeof(addr)); + talk_base::SocketAddress address; + if (result >= 0) { + address.FromSockAddr(addr); + } else { + ASSERT(errno == ENOTCONN); + } + return address; +} + +int +Win32Socket::Bind(const talk_base::SocketAddress& addr) { + ASSERT(socket_ == INVALID_SOCKET); + if (socket_ != INVALID_SOCKET) + return SOCKET_ERROR; + + if (!Create(FD_ACCEPT | FD_CLOSE)) + return SOCKET_ERROR; + + sockaddr_in saddr; + addr.ToSockAddr(&saddr); + int err = ::bind(socket_, (sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + return err; +} + +int +Win32Socket::Connect(const talk_base::SocketAddress& addr) { + ASSERT(socket_ == INVALID_SOCKET); + if (socket_ != INVALID_SOCKET) + return SOCKET_ERROR; + + if (!Create(FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE)) + return SOCKET_ERROR; + + if (!addr.IsUnresolved()) { + sockaddr_in saddr; + addr.ToSockAddr(&saddr); + + // now connect + return DoConnect(saddr); + } + + 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 sockaddr_in& addr) { + connect_time_ = talk_base::GetMillisecondCount(); + int result = connect(socket_, (SOCKADDR*)&addr, sizeof(addr)); + if (result == SOCKET_ERROR) { + int code = WSAGetLastError(); + if (code != WSAEWOULDBLOCK) { + ReportWSAError("WSAAsync:connect", code, addr); + error_ = code; + Close(); + return SOCKET_ERROR; + } + } + addr_ = addr; + state_ = CS_CONNECTING; + return 0; +} + +void +Win32Socket::OnSocketNotify(int event, int error) { + error_ = error; + switch (event) { + case FD_CONNECT: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:connect notify", error, addr_); +#ifdef DEBUG + int32 duration = talk_base::TimeDiff(talk_base::GetMillisecondCount(), + connect_time_); + LOG(LS_INFO) << "WSAAsync:connect error (" << duration + << " ms), faking close"; +#endif + Close(); + // 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 = talk_base::TimeDiff(talk_base::GetMillisecondCount(), + 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_); + Close(); + } else { + SignalReadEvent(this); + } + break; + + case FD_WRITE: + if (error != ERROR_SUCCESS) { + ReportWSAError("WSAAsync:write notify", error, addr_); + Close(); + } else { + SignalWriteEvent(this); + } + break; + + case FD_CLOSE: + ReportWSAError("WSAAsync:close notify", error, addr_); + Close(); + SignalCloseEvent(this, error); + break; + } +} + +void +Win32Socket::OnDnsNotify(int ip, int error) { + LOG_F(LS_INFO) << "(" << talk_base::SocketAddress::IPToString(ip) + << ", " << error << ")"; + if (error == 0) { + talk_base::SocketAddress address(ip, dns_->port); + sockaddr_in addr; + address.ToSockAddr(&addr); + error = DoConnect(addr); + } else { + Close(); + } + + if (error) { + error_ = error; + SignalCloseEvent(this, error_); + } else { + delete dns_; + dns_ = NULL; + } +} + +int +Win32Socket::GetError() const { + return error_; +} + +void +Win32Socket::SetError(int error) { + error_ = error; +} + +Socket::ConnState +Win32Socket::GetState() const { + return state_; +} + +int +Win32Socket::SetOption(Option opt, int value) { + ASSERT(opt == OPT_DONTFRAGMENT); + value = (value == 0) ? 0 : 1; + return ::setsockopt(socket_, IPPROTO_IP, IP_DONTFRAGMENT, + reinterpret_cast<char*>(&value), sizeof(value)); +} + +int +Win32Socket::Send(const void *pv, size_t cb) { + int sent = ::send(socket_, reinterpret_cast<const char *>(pv), (int)cb, 0); + UpdateLastError(); + return sent; +} + +int +Win32Socket::SendTo(const void *pv, size_t cb, + const talk_base::SocketAddress& addr) { + sockaddr_in saddr; + addr.ToSockAddr(&saddr); + int sent = ::sendto(socket_, reinterpret_cast<const char *>(pv), (int)cb, 0, + (sockaddr*)&saddr, sizeof(saddr)); + UpdateLastError(); + return sent; +} + +int +Win32Socket::Recv(void *pv, size_t cb) { + int received = ::recv(socket_, (char *)pv, (int)cb, 0); + UpdateLastError(); + if (signal_close_ && (received > 0)) { + char ch; + if (::recv(socket_, &ch, 1, MSG_PEEK) <= 0) { + signal_close_ = false; + ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY, + WSAMAKESELECTREPLY(FD_CLOSE, 0), 0); + } + } + return received; +} + +int +Win32Socket::RecvFrom(void *pv, size_t cb, talk_base::SocketAddress *paddr) { + sockaddr_in saddr; + socklen_t cbAddr = sizeof(saddr); + int received = ::recvfrom(socket_, (char *)pv, (int)cb, 0, (sockaddr*)&saddr, + &cbAddr); + UpdateLastError(); + if (received != SOCKET_ERROR) + paddr->FromSockAddr(saddr); + if (signal_close_ && (received > 0)) { + char ch; + if (::recv(socket_, &ch, 1, MSG_PEEK) <= 0) { + signal_close_ = false; + ::PostMessage(sink_->handle(), WM_SOCKETNOTIFY, + WSAMAKESELECTREPLY(FD_CLOSE, 0), 0); + } + } + return received; +} + +int +Win32Socket::Listen(int backlog) { + int err = ::listen(socket_, backlog); + UpdateLastError(); + if (err == 0) + state_ = CS_CONNECTING; + return err; +} + +talk_base::Socket* +Win32Socket::Accept(talk_base::SocketAddress *paddr) { + sockaddr_in saddr; + socklen_t cbAddr = sizeof(saddr); + SOCKET s = ::accept(socket_, (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; + signal_close_ = false; + UpdateLastError(); + } + if (dns_) { + WSACancelAsyncRequest(dns_->handle); + delete dns_; + dns_ = NULL; + } + if (sink_) { + sink_->Dispose(); + sink_ = NULL; + } + memset(&addr_, 0, sizeof(addr_)); // no longer connected, zero ip/port + state_ = CS_CLOSED; + return err; +} + +int +Win32Socket::EstimateMTU(uint16* mtu) { + talk_base::SocketAddress addr = GetRemoteAddress(); + if (addr.IsAny()) { + error_ = ENOTCONN; + return -1; + } + + talk_base::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; + talk_base::WinPing::PingResult result = + ping.Ping(addr.ip(), size, 0, 1, false); + if (result == talk_base::WinPing::PING_FAIL) { + error_ = EINVAL; // can't think of a better error ID + return -1; + } + if (result != talk_base::WinPing::PING_TOO_LARGE) { + *mtu = PACKET_MAXIMUMS[level]; + return 0; + } + } + + ASSERT(false); + return 0; +} + +bool +Win32Socket::Create(long events) { + ASSERT(NULL == sink_); + + if (INVALID_SOCKET == socket_) { + socket_ = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, NULL, 0); + if (socket_ == INVALID_SOCKET) { + UpdateLastError(); + return false; + } + } + + // 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; +} + +void +Win32Socket::UpdateLastError() { + error_ = WSAGetLastError(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +/////////////////////////////////////////////////////////////////////////////// + +static UINT s_wm_wakeup_id; + +LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp); + +// A socket server that provides cricket base services on top of a win32 gui thread + +Win32SocketServer::Win32SocketServer(MessageQueue *message_queue) { + if (s_wm_wakeup_id == 0) + s_wm_wakeup_id = RegisterWindowMessage(L"WM_WAKEUP"); + message_queue_ = message_queue; + hwnd_ = NULL; + CreateDummyWindow(); +} + +Win32SocketServer::~Win32SocketServer() { + if (hwnd_ != NULL) { + KillTimer(hwnd_, 1); + ::DestroyWindow(hwnd_); + } +} + +Socket* Win32SocketServer::CreateSocket(int type) { + ASSERT(SOCK_STREAM == type); + return new Win32Socket; +} + +AsyncSocket* Win32SocketServer::CreateAsyncSocket(int type) { + ASSERT(SOCK_STREAM == type); + return new Win32Socket; +} + +bool Win32SocketServer::Wait(int cms, bool process_io) { + ASSERT(!process_io || (cms == 0)); // Should only be used for Thread::Send, or in Pump, below + if (cms == -1) { + MSG msg; + GetMessage(&msg, NULL, s_wm_wakeup_id, s_wm_wakeup_id); + } else if (cms != 0) { + Sleep(cms); + } + return true; +} + +void Win32SocketServer::WakeUp() { + // Always post for every wakeup, so there are no + // critical sections + if (hwnd_ != NULL) + PostMessage(hwnd_, s_wm_wakeup_id, 0, 0); +} + +void Win32SocketServer::Pump() { + // Process messages + Message msg; + while (message_queue_->Get(&msg, 0)) + message_queue_->Dispatch(&msg); + + // Anything remaining? + int delay = message_queue_->GetDelay(); + if (delay == -1) { + KillTimer(hwnd_, 1); + } else { + SetTimer(hwnd_, 1, delay, NULL); + } +} + +void Win32SocketServer::CreateDummyWindow() +{ + static bool s_registered; + if (!s_registered) { + ::WNDCLASSW wc; + memset(&wc, 0, sizeof(wc)); + wc.cbWndExtra = sizeof(this); + wc.lpszClassName = L"Dummy"; + wc.lpfnWndProc = DummyWndProc; + ::RegisterClassW(&wc); + s_registered = true; + } + + hwnd_ = ::CreateWindowW(L"Dummy", L"", 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL); + SetWindowLong(hwnd_, GWL_USERDATA, (LONG)(LONG_PTR)this); +} + +LRESULT CALLBACK DummyWndProc(HWND hwnd, UINT wm, WPARAM wp, LPARAM lp) +{ + if (wm == s_wm_wakeup_id || (wm == WM_TIMER && wp == 1)) { + Win32SocketServer *ss = (Win32SocketServer *)(LONG_PTR)GetWindowLong(hwnd, GWL_USERDATA); + ss->Pump(); + return 0; + } + return ::DefWindowProc(hwnd, wm, wp, lp); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/win32socketserver.h b/third_party/libjingle/files/talk/base/win32socketserver.h new file mode 100644 index 0000000..770141a --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32socketserver.h @@ -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. + */ + +#ifndef TALK_BASE_WIN32SOCKETSERVER_H__ +#define TALK_BASE_WIN32SOCKETSERVER_H__ + +#ifdef WIN32 + +#include "talk/base/messagequeue.h" +#include "talk/base/socketserver.h" +#include "talk/base/socketfactory.h" +#include "talk/base/socket.h" +#include "talk/base/asyncsocket.h" + +namespace talk_base { + +/////////////////////////////////////////////////////////////////////////////// +// Win32Socket +/////////////////////////////////////////////////////////////////////////////// + +class Win32Socket : public talk_base::AsyncSocket { +public: + Win32Socket(); + virtual ~Win32Socket(); + + 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 Socket *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 SetOption(Option opt, int value); + +private: + bool Create(long events); + void UpdateLastError(); + + int DoConnect(const sockaddr_in& addr); + void OnSocketNotify(int event, int error); + void OnDnsNotify(int ip, int error); + + sockaddr_in addr_; // address that we connected to (see DoConnect) + SOCKET socket_; + int error_; + uint32 connect_time_; + ConnState state_; + bool signal_close_; + + class EventSink; + friend class EventSink; + EventSink * sink_; + + struct DnsLookup; + DnsLookup * dns_; +}; + +/////////////////////////////////////////////////////////////////////////////// +// Win32SocketServer +/////////////////////////////////////////////////////////////////////////////// + +class Win32SocketServer : public SocketServer { +public: + Win32SocketServer(MessageQueue *message_queue); + virtual ~Win32SocketServer(); + + // SocketServer Interface + virtual Socket* CreateSocket(int type); + virtual AsyncSocket* CreateAsyncSocket(int type); + virtual bool Wait(int cms, bool process_io); + virtual void WakeUp(); + + void Pump(); + +private: + void CreateDummyWindow(); + + MessageQueue *message_queue_; + HWND hwnd_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // WIN32 + +#endif // TALK_BASE_WIN32SOCKETSERVER_H__ diff --git a/third_party/libjingle/files/talk/base/win32window.h b/third_party/libjingle/files/talk/base/win32window.h new file mode 100644 index 0000000..a4cdb47 --- /dev/null +++ b/third_party/libjingle/files/talk/base/win32window.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 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() { return wnd_; } + + bool Create(HWND parent, const wchar_t* title, DWORD style, DWORD exstyle, + int x, int y, int cx, int cy); + void Destroy(); + +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_; +}; + +/////////////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // WIN32 + +#endif // TALK_BASE_WIN32WINDOW_H__ diff --git a/third_party/libjingle/files/talk/base/winfirewall.cc b/third_party/libjingle/files/talk/base/winfirewall.cc new file mode 100644 index 0000000..9323d01 --- /dev/null +++ b/third_party/libjingle/files/talk/base/winfirewall.cc @@ -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. + */ + +#include <comdef.h> +#include "netfw.h" +#include "winfirewall.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() { + if (mgr_) + 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_); + return SUCCEEDED(hr) && (profile_ != NULL); +} + +void +WinFirewall::Shutdown() { + RELEASE(profile_); + RELEASE(policy_); + RELEASE(mgr_); +} + +bool +WinFirewall::Enabled() { + if (!profile_) + return false; + + VARIANT_BOOL fwEnabled = VARIANT_FALSE; + profile_->get_FirewallEnabled(&fwEnabled); + return (fwEnabled != VARIANT_FALSE); +} + +bool +WinFirewall::Authorized(const char * filename, bool * known) { + if (known) { + *known = false; + } + + if (!profile_) + return false; + + VARIANT_BOOL fwEnabled = VARIANT_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)) { + hr = app->get_Enabled(&fwEnabled); + app->Release(); + if (known) { + *known = true; + } + } else if (hr != HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)) { + // Unexpected error + } + apps->Release(); + } + + return (fwEnabled != VARIANT_FALSE); +} + +bool +WinFirewall::AddApplication(const char * filename, const char * friendly_name, + bool authorized) { + 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(); + } + return SUCCEEDED(hr); +} + +} // namespace talk_base diff --git a/third_party/libjingle/files/talk/base/winfirewall.h b/third_party/libjingle/files/talk/base/winfirewall.h new file mode 100644 index 0000000..09d5a2f --- /dev/null +++ b/third_party/libjingle/files/talk/base/winfirewall.h @@ -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. + */ + +#ifndef TALK_BASE_WINFIREWALL_H__ +#define TALK_BASE_WINFIREWALL_H__ + +struct INetFwMgr; +struct INetFwPolicy; +struct INetFwProfile; + +namespace talk_base { + +////////////////////////////////////////////////////////////////////// +// WinFirewall +////////////////////////////////////////////////////////////////////// + +class WinFirewall { +public: + WinFirewall(); + ~WinFirewall(); + + bool Initialize(); + void Shutdown(); + + bool Enabled(); + bool Authorized(const char * filename, bool * known = 0); + + bool AddApplication(const char * filename, const char * friendly_name, bool authorized = true); + +private: + INetFwMgr * mgr_; + INetFwPolicy * policy_; + INetFwProfile * profile_; +}; + +////////////////////////////////////////////////////////////////////// + +} // namespace talk_base + +#endif // TALK_BASE_WINFIREWALL_H__ diff --git a/third_party/libjingle/files/talk/base/winping.cc b/third_party/libjingle/files/talk/base/winping.cc new file mode 100644 index 0000000..703e6a5 --- /dev/null +++ b/third_party/libjingle/files/talk/base/winping.cc @@ -0,0 +1,317 @@ +/* + * 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/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) + _max((uint32)(8UL), 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/third_party/libjingle/files/talk/base/winping.h b/third_party/libjingle/files/talk/base/winping.h new file mode 100644 index 0000000..0d0b051 --- /dev/null +++ b/third_party/libjingle/files/talk/base/winping.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_WINPING_H__ +#define TALK_BASE_WINPING_H__ + +#ifdef WIN32 + +#if _MSC_VER > 1000 +#pragma once +#endif // _MSC_VER > 1000 + +#include <winsock2.h> +#define _WINSOCKAPI_ +#include <windows.h> +#undef SetPort + +#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/third_party/libjingle/files/talk/p2p/Makefile.am b/third_party/libjingle/files/talk/p2p/Makefile.am new file mode 100644 index 0000000..c935a6b --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/Makefile.am @@ -0,0 +1 @@ +SUBDIRS=base client diff --git a/third_party/libjingle/files/talk/p2p/base/Makefile.am b/third_party/libjingle/files/talk/p2p/base/Makefile.am new file mode 100644 index 0000000..537b6c2 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/Makefile.am @@ -0,0 +1,68 @@ +libcricketp2pbase_la_SOURCES = stun.cc \ + port.cc \ + udpport.cc \ + tcpport.cc \ + stunport.cc \ + relayport.cc \ + stunrequest.cc \ + sessionmanager.cc \ + session.cc \ + transport.cc \ + transportchannel.cc \ + transportchannelproxy.cc \ + p2ptransport.cc \ + p2ptransportchannel.cc \ + rawtransport.cc \ + rawtransportchannel.cc \ + constants.cc \ + pseudotcp.cc + +noinst_HEADERS = candidate.h \ + portallocator.h \ + relayport.h \ + session.h \ + stunport.h \ + tcpport.h \ + port.h \ + sessionid.h \ + stunrequest.h \ + udpport.h \ + pseudotcp.h \ + sessiondescription.h \ + sessionmanager.h \ + stun.h \ + relayserver.h \ + stunserver.h \ + sessionclient.h \ + transport.h \ + transportchannel.h \ + transportchannelproxy.h \ + transportchannelimpl.h \ + p2ptransport.h \ + p2ptransportchannel.h \ + rawtransport.h \ + rawtransportchannel.h \ + constants.h \ + common.h + +AM_CPPFLAGS = -DPOSIX -DENABLE_DEBUG -D_DEBUG -g + +P2PLIBS = libcricketp2pbase.la ../../base/libcricketbase.la -lpthread +XMLLIBS = ../../xmllite/libcricketxmllite.la ../../xmpp/libcricketxmpp.la $(EXPAT_LIBS) +TESTLIBS = ../../base/libcrickettest.la + +bin_PROGRAMS = relayserver stunserver +noinst_PROGRAMS = stunserver_unittest session_unittest port_unittest + +relayserver_SOURCES = relayserver.cc relayserver_main.cc +relayserver_LDADD = $(P2PLIBS) +stunserver_SOURCES = stunserver.cc stunserver_main.cc +stunserver_LDADD = $(P2PLIBS) +stunserver_unittest_SOURCES = stunserver_unittest.cc stunserver.cc +stunserver_unittest_LDADD = $(P2PLIBS) $(TESTLIBS) +session_unittest_SOURCES = session_unittest.cc stunserver.cc relayserver.cc +session_unittest_LDADD = $(TESTLIBS) $(P2PLIBS) $(XMLLIBS) +port_unittest_SOURCES = port_unittest.cc stunserver.cc relayserver.cc +port_unittest_LDADD = $(P2PLIBS) $(TESTLIBS) + +noinst_LTLIBRARIES = libcricketp2pbase.la diff --git a/third_party/libjingle/files/talk/p2p/base/candidate.h b/third_party/libjingle/files/talk/p2p/base/candidate.h new file mode 100644 index 0000000..ac41311 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/candidate.h @@ -0,0 +1,119 @@ +/* + * 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 _CANDIDATE_H_ +#define _CANDIDATE_H_ + +#include <string> +#include <sstream> +#include "talk/base/socketaddress.h" + +namespace cricket { + +// Candidate for ICE based connection discovery. + +class Candidate { +public: + + 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; } + + const 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_); + } + +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 // _CANDIDATE_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/common.h b/third_party/libjingle/files/talk/p2p/base/common.h new file mode 100644 index 0000000..72f8cfa --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/common.h @@ -0,0 +1,36 @@ +/* + * 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_COMMON_H__ +#define CRICKET_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() << ": " + +#endif // CRICKET_P2P_BASE_COMMON_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/constants.cc b/third_party/libjingle/files/talk/p2p/base/constants.cc new file mode 100644 index 0000000..13b9f2d --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/constants.cc @@ -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. + */ + +#include "talk/p2p/base/constants.h" + +namespace cricket { + +const std::string NS_EMPTY(""); +const std::string NS_GOOGLESESSION("http://www.google.com/session"); +#ifdef FEATURE_ENABLE_VOICEMAIL +const std::string NS_GOOGLEVOICEMAIL("http://www.google.com/session/voicemail"); +#endif + +const buzz::QName QN_SESSION(true, NS_GOOGLESESSION, "session"); + +const buzz::QName QN_REDIRECT_TARGET(true, NS_GOOGLESESSION, "target"); +const buzz::QName QN_REDIRECT_COOKIE(true, NS_GOOGLESESSION, "cookie"); +const buzz::QName QN_REDIRECT_REGARDING(true, NS_GOOGLESESSION, "regarding"); + +#ifdef FEATURE_ENABLE_VOICEMAIL +const buzz::QName QN_VOICEMAIL_REGARDING(true, NS_GOOGLEVOICEMAIL, "regarding"); +#endif + +const buzz::QName QN_INITIATOR(true, NS_EMPTY, "initiator"); + +const buzz::QName QN_ADDRESS(true, cricket::NS_EMPTY, "address"); +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_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 buzz::QName QN_PROTOCOL(true, cricket::NS_EMPTY, "protocol"); + +// Legacy transport messages +const buzz::QName kQnLegacyCandidate(true, cricket::NS_GOOGLESESSION, + "candidate"); +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/constants.h b/third_party/libjingle/files/talk/p2p/base/constants.h new file mode 100644 index 0000000..46315eb --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/constants.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 _CRICKET_P2P_BASE_CONSTANTS_H_ +#define _CRICKET_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 { + +extern const std::string NS_EMPTY; +extern const std::string NS_GOOGLESESSION; +#ifdef FEATURE_ENABLE_VOICEMAIL +extern const std::string NS_GOOGLEVOICEMAIL; +#endif + +extern const buzz::QName QN_SESSION; + +extern const buzz::QName QN_REDIRECT_TARGET; +extern const buzz::QName QN_REDIRECT_COOKIE; +extern const buzz::QName QN_REDIRECT_REGARDING; +#ifdef FEATURE_ENABLE_VOICEMAIL +extern const buzz::QName QN_VOICEMAIL_REGARDING; +#endif + +extern const buzz::QName QN_INITIATOR; + +extern const buzz::QName QN_ADDRESS; +extern const buzz::QName QN_PORT; +extern const buzz::QName QN_NETWORK; +extern const buzz::QName QN_GENERATION; +extern const buzz::QName QN_USERNAME; +extern const buzz::QName QN_PASSWORD; +extern const buzz::QName QN_PREFERENCE; +extern const buzz::QName QN_PROTOCOL; + +// Legacy transport messages +extern const buzz::QName kQnLegacyCandidate; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_CONSTANTS_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/p2ptransport.cc b/third_party/libjingle/files/talk/p2p/base/p2ptransport.cc new file mode 100644 index 0000000..2221d8c --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/p2ptransport.cc @@ -0,0 +1,209 @@ +/* + * 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 "talk/base/common.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/constants.h" +#include "talk/base/helpers.h" +#include "talk/p2p/base/p2ptransportchannel.h" +#include "talk/p2p/base/sessionmanager.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 { + +const std::string kNsP2pTransport("http://www.google.com/transport/p2p"); +const buzz::QName kQnP2pTransport(true, kNsP2pTransport, "transport"); +const buzz::QName kQnP2pCandidate(true, kNsP2pTransport, "candidate"); +const buzz::QName kQnP2pUnknownChannelName(true, kNsP2pTransport, + "unknown-channel-name"); + +P2PTransport::P2PTransport(SessionManager* session_manager) + : Transport(session_manager, kNsP2pTransport) { +} + +P2PTransport::~P2PTransport() { + DestroyAllChannels(); +} + +buzz::XmlElement* P2PTransport::CreateTransportOffer() { + return new buzz::XmlElement(kQnP2pTransport, true); +} + +buzz::XmlElement* P2PTransport::CreateTransportAnswer() { + return new buzz::XmlElement(kQnP2pTransport, true); +} + +bool P2PTransport::OnTransportOffer(const buzz::XmlElement* elem) { + ASSERT(elem->Name() == kQnP2pTransport); + // We don't support any options, so we ignore them. + return true; +} + +bool P2PTransport::OnTransportAnswer(const buzz::XmlElement* elem) { + ASSERT(elem->Name() == kQnP2pTransport); + // We don't support any options. We fail if any are given. The other side + // should know from our request that we expected an empty response. + return elem->FirstChild() == NULL; +} + +bool P2PTransport::OnTransportMessage(const buzz::XmlElement* msg, + const buzz::XmlElement* stanza) { + ASSERT(msg->Name() == kQnP2pTransport); + for (const buzz::XmlElement* elem = msg->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + if (elem->Name() == kQnP2pCandidate) { + // Make sure this candidate is valid. + Candidate candidate; + if (!ParseCandidate(stanza, elem, &candidate)) + return false; + + ForwardChannelMessage(elem->Attr(buzz::QN_NAME), + new buzz::XmlElement(*elem)); + } + } + return true; +} + +bool P2PTransport::OnTransportError(const buzz::XmlElement* session_msg, + const buzz::XmlElement* error) { + ASSERT(error->Name().Namespace() == kNsP2pTransport); + if ((error->Name() == kQnP2pUnknownChannelName) + && error->HasAttr(buzz::QN_NAME)) { + std::string channel_name = error->Attr(buzz::QN_NAME); + if (HasChannel(channel_name)) { + SignalChannelGone(this, channel_name); + } + } + return true; +} + +void P2PTransport::OnTransportChannelMessages( + const std::vector<buzz::XmlElement*>& candidates) { + buzz::XmlElement* transport = + new buzz::XmlElement(kQnP2pTransport, true); + for (size_t i = 0; i < candidates.size(); ++i) + transport->AddElement(candidates[i]); + + std::vector<buzz::XmlElement*> elems; + elems.push_back(transport); + SignalTransportMessage(this, elems); +} + +bool P2PTransport::ParseCandidate(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + Candidate* candidate) { + // Check for all of the required attributes. + 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 BadRequest(stanza, "candidate missing required attribute", NULL); + } + + // Make sure the channel named actually exists. + if (!HasChannel(elem->Attr(buzz::QN_NAME))) { + scoped_ptr<buzz::XmlElement> + extra_info(new buzz::XmlElement(kQnP2pUnknownChannelName)); + extra_info->AddAttr(buzz::QN_NAME, elem->Attr(buzz::QN_NAME)); + return BadRequest(stanza, "channel named in candidate does not exist", + extra_info.get()); + } + + // Parse the address given. + talk_base::SocketAddress address; + if (!ParseAddress(stanza, elem, &address)) + 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)); + + // Check that the username is not too long and does not use any bad chars. + if (candidate->username().size() > kMaxUsernameSize) + return BadRequest(stanza, "candidate username is too long", NULL); + if (!IsBase64Encoded(candidate->username())) + return BadRequest(stanza, + "candidate username has non-base64 encoded characters", + NULL); + + // Look for the non-required attributes. + 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)); + + return true; +} + +buzz::XmlElement* P2PTransport::TranslateCandidate(const Candidate& c) { + buzz::XmlElement* candidate = new buzz::XmlElement(kQnP2pCandidate); + candidate->SetAttr(buzz::QN_NAME, c.name()); + candidate->SetAttr(QN_ADDRESS, c.address().IPAsString()); + candidate->SetAttr(QN_PORT, c.address().PortAsString()); + candidate->SetAttr(QN_PREFERENCE, c.preference_str()); + candidate->SetAttr(QN_USERNAME, c.username()); + candidate->SetAttr(QN_PROTOCOL, c.protocol()); + candidate->SetAttr(QN_GENERATION, c.generation_str()); + if (c.password().size() > 0) + candidate->SetAttr(QN_PASSWORD, c.password()); + if (c.type().size() > 0) + candidate->SetAttr(buzz::QN_TYPE, c.type()); + if (c.network_name().size() > 0) + candidate->SetAttr(QN_NETWORK, c.network_name()); + return candidate; +} + +TransportChannelImpl* P2PTransport::CreateTransportChannel( + const std::string& name, const std::string &session_type) { + return new P2PTransportChannel( + name, session_type, this, session_manager()->port_allocator()); +} + +void P2PTransport::DestroyTransportChannel(TransportChannelImpl* channel) { + delete channel; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/p2ptransport.h b/third_party/libjingle/files/talk/p2p/base/p2ptransport.h new file mode 100644 index 0000000..2027d4a --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/p2ptransport.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 _CRICKET_P2P_BASE_P2PTRANSPORT_H_ +#define _CRICKET_P2P_BASE_P2PTRANSPORT_H_ + +#include "talk/p2p/base/transport.h" + +namespace cricket { + +class Candidate; + +// Xml names used to name this transport and create our elements +extern const std::string kNsP2pTransport; +extern const buzz::QName kQnP2pTransport; +extern const buzz::QName kQnP2pCandidate; + +class P2PTransport: public Transport { + public: + P2PTransport(SessionManager* session_manager); + virtual ~P2PTransport(); + + // Implements negotiation of the P2P protocol. + virtual buzz::XmlElement* CreateTransportOffer(); + virtual buzz::XmlElement* CreateTransportAnswer(); + virtual bool OnTransportOffer(const buzz::XmlElement* elem); + virtual bool OnTransportAnswer(const buzz::XmlElement* elem); + + // Forwards each candidate message to the appropriate channel. + virtual bool OnTransportMessage(const buzz::XmlElement* msg, + const buzz::XmlElement* stanza); + virtual bool OnTransportError(const buzz::XmlElement* session_msg, + const buzz::XmlElement* error); + + protected: + // Creates and destroys P2PTransportChannel. + virtual TransportChannelImpl* CreateTransportChannel(const std::string& name, const std::string &session_type); + virtual void DestroyTransportChannel(TransportChannelImpl* channel); + + // Sends a given set of channel messages, which each describe a candidate, + // to the other client as a single transport message. + void OnTransportChannelMessages( + const std::vector<buzz::XmlElement*>& candidates); + + private: + // Attempts to parse the given XML into a candidate. Returns true if the + // XML is valid. If not, we will signal an error. + bool ParseCandidate(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + Candidate* candidate); + + // Generates a XML element describing the given candidate. + buzz::XmlElement* TranslateCandidate(const Candidate& c); + + friend class P2PTransportChannel; + + DISALLOW_EVIL_CONSTRUCTORS(P2PTransport); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_P2PTRANSPORT_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.cc b/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.cc new file mode 100644 index 0000000..717ae70 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.cc @@ -0,0 +1,911 @@ +/* + * 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 <errno.h> + +#include <iostream> + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/p2p/base/common.h" +#include "talk/p2p/base/p2ptransportchannel.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 MOS 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 == NULL) || (b_conn == NULL)) // 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 &session_type, + P2PTransport* transport, + PortAllocator *allocator) +: TransportChannelImpl(name, session_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 + OnAllocate(); + + // 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()) + OnAllocate(); + + // 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) { + buzz::XmlElement* msg = transport_->TranslateCandidate(candidates[i]); + SignalChannelMessage(this, msg); + } +} + +// 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; +} + +// We received a candidate from the other side, make connections so we +// can try to use these remote candidates with our local candidates. +void P2PTransportChannel::OnChannelMessage(const buzz::XmlElement* msg) { + ASSERT(worker_thread_ == talk_base::Thread::Current()); + + Candidate remote_candidate; + bool valid = transport_->ParseCandidate(NULL, msg, &remote_candidate); + ASSERT(valid); + + // Create connections to this remote candidate. + CreateConnections(remote_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 iff 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) && + 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); + } + + // 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; +} + +// 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: the previous best_connection_ may be destroyed by now, so don't + // use it. + best_connection_ = conn; + if (best_connection_) { + LOG_J(LS_VERBOSE, this) << "New best connection: " << conn->ToString(); + SignalRouteChange(this, best_connection_->remote_candidate().address()); + } +} + +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()); + + 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. + thread()->Clear(this, MSG_ALLOCATE); + } + + // 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; + OnAllocate(); + } + + // 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) + OnAllocate(); + } + + // 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) + OnAllocate(); + 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 = + 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 = 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; +} + +// Time for a new allocator, lets make sure we have a signalling channel +// to communicate candidates through first. +void P2PTransportChannel::OnAllocate() { + waiting_for_signaling_ = true; + SignalRequestSignaling(); +} + +// 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(), session_type())); + thread()->PostDelayed(kAllocatePeriod, this, MSG_ALLOCATE); + } +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.h b/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.h new file mode 100644 index 0000000..bea1537 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/p2ptransportchannel.h @@ -0,0 +1,156 @@ +/* + * 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 _CRICKET_P2P_BASE_P2PTRANSPORTCHANNEL_H_ +#define _CRICKET_P2P_BASE_P2PTRANSPORTCHANNEL_H_ + +#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 &session_type, + P2PTransport* transport, + PortAllocator *allocator); + virtual ~P2PTransportChannel(); + + // From TransportChannelImpl: + virtual Transport* GetTransport() { return transport_; } + virtual void Connect(); + virtual void Reset(); + virtual void OnSignalingReady(); + virtual void OnChannelMessage(const buzz::XmlElement* msg); + + // 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_; } + + // 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); + + private: + 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 OnAllocate(); + 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_; + bool pinging_started_; // indicates whether StartGetAllCandidates has been called + 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 // _CRICKET_P2P_BASE_P2PTRANSPORTCHANNEL_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/port.cc b/third_party/libjingle/files/talk/p2p/base/port.cc new file mode 100644 index 0000000..cc33d4c --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/port.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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif + +#include <errno.h> + +#include <algorithm> +#include <iostream> +#include <vector> + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/base/helpers.h" +#include "talk/base/logging.h" +#include "talk/base/scoped_ptr.h" +#include "talk/base/socketadapters.h" +#include "talk/p2p/base/common.h" +#include "talk/p2p/base/port.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcmp; +} +#endif + +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 and the number +// of data points on which it is based. +inline uint32 ConservativeRTTEstimate(uint32 rtt, uint32 rtt_data_points) { + if (rtt_data_points == 0) + return DEFAULT_RTT; + else + 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[PROTO_LAST+1] = { "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; +} + +std::string Port::agent_; +talk_base::ProxyInfo Port::proxy_; + +Port::Port(talk_base::Thread* thread, const std::string& type, + talk_base::SocketFactory* factory, talk_base::Network* network) + : thread_(thread), factory_(factory), type_(type), network_(network), + preference_(-1), lifetime_(LT_PRESTART), enable_port_packets_(false) { + + if (factory_ == NULL) + factory_ = thread_->socketserver(); + + set_username_fragment(CreateRandomString(16)); + set_password(CreateRandomString(16)); +} + +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 { + LOG_J(LS_ERROR, this) << "Received unexpected STUN message type (" + << msg->type() << ") from unknown address (" + << addr.ToString() << ")"; + delete msg; + } +} + +void Port::SendBindingRequest(Connection* conn) { + + // Construct the request message. + + StunMessage request; + request.SetType(STUN_BINDING_REQUEST); + request.SetTransactionID(CreateRandomString(16)); + + StunByteStringAttribute* username_attr = + StunAttribute::CreateByteString(STUN_ATTR_USERNAME); + std::string username = conn->remote_candidate().username(); + username.append(username_frag_); + username_attr->CopyBytes(username.c_str(), (uint16)username.size()); + request.AddAttribute(username_attr); + + // Send the request message. + // NOTE: If we wanted to, this is where we would add the HMAC. + talk_base::ByteBuffer buf; + request.Write(&buf); + SendTo(buf.Data(), buf.Length(), conn->remote_candidate().address(), false); +} + +bool Port::GetStunMessage(const char* data, size_t size, + const talk_base::SocketAddress& addr, + StunMessage *& msg, std::string& remote_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. + + msg = 0; + + // Parse the request message. If the packet is not a complete and correct + // STUN message, then ignore it. + 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) + || (std::memcmp(username_attr->bytes(), + username_frag_.c_str(), username_frag_.size()) != 0)) { + LOG_J(LS_ERROR, this) << "Received STUN request with bad username"; + SendBindingErrorResponse(stun_msg.get(), addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + return true; + } + + remote_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) + || (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 username"; + // Do not send error response to a response + return true; + } + + remote_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=" << error_code->error_class() + << " number=" << error_code->number() + << " reason='" << error_code->reason() << "'"; + // Return message to allow error-specific processing + } else { + LOG_J(LS_ERROR, this) + << "Received STUN error response with no error code"; + // Drop corrupt message + return true; + } + } + } else { + LOG_J(LS_ERROR, this) << "Received STUN packet with invalid type (" + << stun_msg->type() << ")"; + return true; + } + + // Return the STUN message found. + 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); + + // 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); + SendTo(buf.Data(), buf.Length(), addr, false); + + // 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); + 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); + + // 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); +} + +talk_base::AsyncPacketSocket* Port::CreatePacketSocket(ProtocolType proto) { + if (proto == PROTO_UDP) { + return new talk_base::AsyncUDPSocket( + factory_->CreateAsyncSocket(SOCK_DGRAM)); + } else if ((proto == PROTO_TCP) || (proto == PROTO_SSLTCP)) { + talk_base::AsyncSocket * socket = factory_->CreateAsyncSocket(SOCK_STREAM); + switch (proxy().type) { + case talk_base::PROXY_NONE: + break; + case talk_base::PROXY_SOCKS5: + socket = new talk_base::AsyncSocksProxySocket( + socket, proxy().address, proxy().username, proxy().password); + break; + case talk_base::PROXY_HTTPS: + default: + socket = new talk_base::AsyncHttpsProxySocket( + socket, user_agent(), proxy().address, proxy().username, + proxy().password); + break; + } + if (proto == PROTO_SSLTCP) { + socket = new talk_base::AsyncSSLSocket(socket); + } + return new talk_base::AsyncTCPSocket(socket); + } else { + LOG_J(LS_INFO, this) << "Unknown protocol (" << proto << ")"; + return 0; + } +} + +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: + 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(), (uint16)username.size()); + request->AddAttribute(username_attr); + } + + virtual void OnResponse(StunMessage* response) { + connection_->OnConnectionRequestResponse(response, Elapsed()); + } + + virtual void OnErrorResponse(StunMessage* response) { + connection_->OnConnectionRequestErrorResponse(response, Elapsed()); + } + + virtual void OnTimeout() { + } + + 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) + : requests_(port->thread()), port_(port), local_candidate_index_(index), + remote_candidate_(remote_candidate), read_state_(STATE_READ_TIMEOUT), + write_state_(STATE_WRITE_CONNECT), connected_(true), pruned_(false), + rtt_(0), rtt_data_points_(0), last_ping_sent_(0), last_ping_received_(0), + recv_total_bytes_(0), recv_bytes_second_(0), + last_recv_bytes_second_time_((uint32)-1), last_recv_bytes_second_calc_(0), + sent_total_bytes_(0), sent_bytes_second_(0), + last_sent_bytes_second_time_((uint32)-1), last_sent_bytes_second_calc_(0), + reported_(false) { + + // Wire up to send stun packets + requests_.SignalSendPacket.connect(this, &Connection::OnSendStunPacket); +} + +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) { + port_->SendTo(data, size, remote_candidate_.address(), false); +} + +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! + + recv_total_bytes_ += 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 send 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 + } else if (remote_username != remote_candidate_.username()) { + // Not destined this connection + LOG_J(LS_ERROR, this) << "Received STUN packet on wrong address."; + if (msg->type() == STUN_BINDING_REQUEST) { + port_->SendBindingErrorResponse(msg, addr, STUN_ERROR_BAD_REQUEST, + STUN_ERROR_REASON_BAD_REQUEST); + } + delete msg; + } else { + // The packet is STUN, with the current 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) { + // 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)) { + 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. + + uint32 rtt = ConservativeRTTEstimate(rtt_, rtt_data_points_); + + 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)) { + set_write_state(STATE_WRITE_CONNECT); + } + + if ((write_state_ == STATE_WRITE_CONNECT) && + TooLongWithoutResponse(pings_since_last_response_, + CONNECTION_WRITE_TIMEOUT, + now)) { + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +void Connection::Ping(uint32 now) { + assert(connected_); + last_ping_sent_ = now; + pings_since_last_response_.push_back(now); + requests_.Send(new ConnectionRequest(this)); +} + +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.address().ToString() + << "->" << remote.name() << ":" << remote.type() << ":" << remote.address().ToString() + << "|" + << CONNECT_STATE_ABBREV[connected()] + << READ_STATE_ABBREV[read_state()] + << WRITE_STATE_ABBREV[write_state()] + << "]"; + return ss.str(); +} + +void Connection::OnConnectionRequestResponse(StunMessage *response, uint32 rtt) { + // We have a potentially valid reply from the remote address. + // The packet must include a username that ends with our fragment, + // since it is a response. + + // Check exact message type + bool valid = true; + if (response->type() != STUN_BINDING_RESPONSE) + valid = false; + + // Must have username attribute + const StunByteStringAttribute* username_attr = + response->GetByteString(STUN_ATTR_USERNAME); + if (valid) { + if (!username_attr) { + LOG_J(LS_ERROR, this) << "Received likely STUN packet with no username"; + valid = false; + } + } + + // Length must be at least the size of our fragment (actually, should + // be bigger since our fragment is at the end!) + if (valid) { + if (username_attr->length() <= port_->username_fragment().size()) { + LOG_J(LS_ERROR, this) << "Received likely STUN packet with short username"; + valid = false; + } + } + + // Compare our fragment with the end of the username - must be exact match + if (valid) { + std::string username_fragment = port_->username_fragment(); + int offset = (int)(username_attr->length() - username_fragment.size()); + if (std::memcmp(username_attr->bytes() + offset, + username_fragment.c_str(), username_fragment.size()) != 0) { + LOG_J(LS_ERROR, this) << "Received STUN response with bad username"; + valid = false; + } + } + + if (valid) { + // Valid response. 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. + set_write_state(STATE_WRITABLE); + + pings_since_last_response_.clear(); + rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1); + rtt_data_points_ += 1; + } +} + +void Connection::OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt) { + const StunErrorCodeAttribute* error = response->GetErrorCode(); + uint32 error_code = error ? error->error_code() : 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. + set_write_state(STATE_WRITE_TIMEOUT); + } +} + +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 connectoin. + 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() { + // Snapshot bytes / second calculator + + uint32 current_time = talk_base::Time(); + if (last_recv_bytes_second_time_ != (uint32)-1) { + int delta = talk_base::TimeDiff(current_time, last_recv_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(recv_total_bytes_ - last_recv_bytes_second_calc_) * fraction_time / delta; + recv_bytes_second_ = (recv_total_bytes_ - last_recv_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_recv_bytes_second_time_ = current_time - fraction_time; + last_recv_bytes_second_calc_ = recv_total_bytes_ - fraction_bytes; + } + } + if (last_recv_bytes_second_time_ == (uint32)-1) { + last_recv_bytes_second_time_ = current_time; + last_recv_bytes_second_calc_ = recv_total_bytes_; + } + + return recv_bytes_second_; +} + +size_t Connection::recv_total_bytes() { + return recv_total_bytes_; +} + +size_t Connection::sent_bytes_second() { + // Snapshot bytes / second calculator + + uint32 current_time = talk_base::Time(); + if (last_sent_bytes_second_time_ != (uint32)-1) { + int delta = talk_base::TimeDiff(current_time, last_sent_bytes_second_time_); + if (delta >= 1000) { + int fraction_time = delta % 1000; + int seconds_time = delta - fraction_time; + int fraction_bytes = (int)(sent_total_bytes_ - last_sent_bytes_second_calc_) * fraction_time / delta; + sent_bytes_second_ = (sent_total_bytes_ - last_sent_bytes_second_calc_ - fraction_bytes) * seconds_time / delta; + last_sent_bytes_second_time_ = current_time - fraction_time; + last_sent_bytes_second_calc_ = sent_total_bytes_ - fraction_bytes; + } + } + if (last_sent_bytes_second_time_ == (uint32)-1) { + last_sent_bytes_second_time_ = current_time; + last_sent_bytes_second_calc_ = sent_total_bytes_; + } + + return sent_bytes_second_; +} + +size_t Connection::sent_total_bytes() { + return sent_total_bytes_; +} + +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 { + sent_total_bytes_ += sent; + } + return sent; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/port.h b/third_party/libjingle/files/talk/p2p/base/port.h new file mode 100644 index 0000000..57c0727 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/port.h @@ -0,0 +1,421 @@ +/* + * 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 __PORT_H__ +#define __PORT_H__ + +#include "talk/base/network.h" +#include "talk/base/socketaddress.h" +#include "talk/base/proxyinfo.h" +#include "talk/base/sigslot.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/candidate.h" +#include "talk/p2p/base/stun.h" +#include "talk/p2p/base/stunrequest.h" + +#include <string> +#include <vector> +#include <map> + +namespace talk_base { +class AsyncPacketSocket; +} + +namespace cricket { + +class Connection; + +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::SocketFactory* factory, talk_base::Network* network); + 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::SocketFactory* socket_factory() const { return factory_; } + void set_socket_factory(talk_base::SocketFactory* 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. + const std::string& username_fragment() const { return username_frag_; } + const std::string& password() const { return 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& protocol() const { return proto_; } + 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; + + static void set_proxy(const std::string& user_agent, + const talk_base::ProxyInfo& proxy) { + agent_ = user_agent; proxy_ = proxy; + } + static const std::string& user_agent() { return agent_; } + static const talk_base::ProxyInfo& proxy() { return proxy_; } + + talk_base::AsyncPacketSocket * CreatePacketSocket(ProtocolType proto); + + // 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: + talk_base::Thread* thread_; + talk_base::SocketFactory* factory_; + std::string type_; + talk_base::Network* network_; + 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_; + + // Fills in the username fragment and password. These will be initially set + // in the constructor to random values. Subclasses can override, though. + void set_username_fragment(const std::string& username_fragment) { + username_frag_ = username_fragment; + } + void set_password(const std::string& password) { password_ = password; } + + // 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); + + // Constructs a STUN binding request for the given connection and sends it. + void SendBindingRequest(Connection* conn); + + // 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 *& msg, std::string& remote_username); + + friend class Connection; + +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(); + + static std::string agent_; + static talk_base::ProxyInfo proxy_; +}; + +// 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() { 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() { 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() { return reported_; } + void set_reported(bool reported) { reported_ = reported;} + +protected: + 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 rtt_data_points_; + 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 + std::vector<uint32> pings_since_last_response_; + + size_t recv_total_bytes_; + size_t recv_bytes_second_; + uint32 last_recv_bytes_second_time_; + size_t last_recv_bytes_second_calc_; + + size_t sent_total_bytes_; + size_t sent_bytes_second_; + uint32 last_sent_bytes_second_time_; + size_t last_sent_bytes_second_calc_; + + // Callbacks from ConnectionRequest + void OnConnectionRequestResponse(StunMessage *response, uint32 rtt); + void OnConnectionRequestErrorResponse(StunMessage *response, uint32 rtt); + + // Called back when StunRequestManager has a stun packet to send + void OnSendStunPacket(const void* data, size_t size, StunRequest* req); + + // Constructs a new connection to the given remote port. + Connection(Port* port, size_t index, const Candidate& candidate); + + // 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); + + friend class Port; + friend class ConnectionRequest; + +private: + bool reported_; +}; + +// 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 // __PORT_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/port_unittest.cc b/third_party/libjingle/files/talk/p2p/base/port_unittest.cc new file mode 100644 index 0000000..4687c19 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/port_unittest.cc @@ -0,0 +1,363 @@ +#include "talk/base/helpers.h" +#include "talk/base/host.h" +#include "talk/base/natserver.h" +#include "talk/base/natsocketfactory.h" +#include "talk/base/socketaddress.h" +#include "talk/base/thread.h" +#include "talk/base/virtualsocketserver.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/relayserver.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/stunserver.h" +#include <iostream> + +using namespace cricket; + +const uint32 MSG_CONNECT = 1; +const uint32 MSG_PREP_ADDRESS = 2; +const uint32 MSG_CREATE_CONN = 3; +const uint32 MSG_ACCEPT_CONN = 4; +const uint32 MSG_PING = 5; + +Candidate GetCandidate(Port* port) { + assert(port->candidates().size() == 1); + return port->candidates()[0]; +} + +talk_base::SocketAddress GetAddress(Port* port) { + return GetCandidate(port).address(); +} + +struct Foo : public talk_base::MessageHandler, public sigslot::has_slots<> { + int count; + talk_base::SocketAddress address; + StunMessage* request; + std::string remote_frag; + + talk_base::Thread* thread; + Port* port1; + Port* port2; + Connection* conn; + + Foo(talk_base::Thread* th, Port* p1, Port* p2) + : count(0), thread(th), port1(p1), port2(p2), conn(0) { + } + + void OnAddressReady(Port* port) { + count += 1; + } + + void OnUnknownAddress( + Port* port, const talk_base::SocketAddress& addr, StunMessage* msg, + const std::string& rf) { + assert(port == port1); + if (!address.IsAny()) { + assert(addr == address); + delete request; + } + address = addr; + request = msg; + remote_frag = rf; + } + + void OnMessage(talk_base::Message* pmsg) { + assert(talk_base::Thread::Current() == thread); + + switch (pmsg->message_id) { + case MSG_CONNECT: + port1->SignalAddressReady.connect(this, &Foo::OnAddressReady); + port1->SignalUnknownAddress.connect(this, &Foo::OnUnknownAddress); + break; + + case MSG_PREP_ADDRESS: + port1->PrepareAddress(); + break; + + case MSG_CREATE_CONN: + conn = port1->CreateConnection(GetCandidate(port2), Port::ORIGIN_MESSAGE); + assert(conn); + conn->Ping(0); + break; + + case MSG_PING: + assert(conn); + conn->Ping(0); + break; + + case MSG_ACCEPT_CONN: { + Candidate c = GetCandidate(port2); + c.set_address(address); + conn = port1->CreateConnection(c, Port::ORIGIN_MESSAGE); + assert(conn); + port1->SendBindingResponse(request, address); + conn->Ping(0); + delete request; + break; + } + + default: + assert(false); + break; + } + } +}; + +void test(talk_base::Thread* pthMain, const char* name1, Port* port1, + talk_base::Thread* pthBack, const char* name2, Port* port2, + bool accept = true, bool same_addr = true) { + Foo* foo1 = new Foo(pthMain, port1, port2); + Foo* foo2 = new Foo(pthBack, port2, port1); + + std::cout << "Test: " << name1 << " to " << name2 << ": "; + std::cout.flush(); + + pthBack->Start(); + + pthMain->Post(foo1, MSG_CONNECT); + pthBack->Post(foo2, MSG_CONNECT); + pthMain->ProcessMessages(10); + assert(foo1->count == 0); + assert(foo2->count == 0); + + pthMain->Post(foo1, MSG_PREP_ADDRESS); + pthMain->ProcessMessages(200); + assert(foo1->count == 1); + + pthBack->Post(foo2, MSG_PREP_ADDRESS); + pthMain->ProcessMessages(200); + assert(foo2->count == 1); + + pthMain->Post(foo1, MSG_CREATE_CONN); + pthMain->ProcessMessages(200); + + if (accept) { + + assert(foo1->address.IsAny()); + assert(foo2->remote_frag == port1->username_fragment()); + assert(!same_addr || (foo2->address == GetAddress(port1))); + + pthBack->Post(foo2, MSG_ACCEPT_CONN); + pthMain->ProcessMessages(200); + + } else { + + assert(foo1->address.IsAny()); + assert(foo2->address.IsAny()); + + pthBack->Post(foo2, MSG_CREATE_CONN); + pthMain->ProcessMessages(200); + + if (same_addr) { + assert(foo1->conn->read_state() == Connection::STATE_READABLE); + assert(foo2->conn->write_state() == Connection::STATE_WRITABLE); + + // First connection may not be writable if the first ping did not get + // through. So we will have to do another. + if (foo1->conn->write_state() == Connection::STATE_WRITE_CONNECT) { + pthMain->Post(foo1, MSG_PING); + pthMain->ProcessMessages(200); + } + } else { + assert(foo1->address.IsAny()); + assert(foo2->address.IsAny()); + + pthMain->Post(foo1, MSG_PING); + pthMain->ProcessMessages(200); + + assert(foo1->address.IsAny()); + assert(!foo2->address.IsAny()); + + pthBack->Post(foo2, MSG_ACCEPT_CONN); + pthMain->ProcessMessages(200); + } + } + + assert(foo1->conn->read_state() == Connection::STATE_READABLE); + assert(foo1->conn->write_state() == Connection::STATE_WRITABLE); + assert(foo2->conn->read_state() == Connection::STATE_READABLE); + assert(foo2->conn->write_state() == Connection::STATE_WRITABLE); + + pthBack->Stop(); + + delete port1; + delete port2; + delete foo1; + delete foo2; + + std::cout << "PASS" << std::endl; +} + +const talk_base::SocketAddress local_addr = talk_base::SocketAddress("127.0.0.1", 0); +const talk_base::SocketAddress relay_int_addr = talk_base::SocketAddress("127.0.0.1", 5000); +const talk_base::SocketAddress relay_ext_addr = talk_base::SocketAddress("127.0.0.1", 5001); +const talk_base::SocketAddress stun_addr = talk_base::SocketAddress("127.0.0.1", STUN_SERVER_PORT); +const talk_base::SocketAddress nat_addr = talk_base::SocketAddress("127.0.0.1", talk_base::NAT_SERVER_PORT); + +void test_udp() { + talk_base::Thread* pthMain = talk_base::Thread::Current(); + talk_base::Thread* pthBack = new talk_base::Thread(); + talk_base::Network* network = new talk_base::Network("network", local_addr.ip()); + + test(pthMain, "udp", new UDPPort(pthMain, NULL, network, local_addr), + pthBack, "udp", new UDPPort(pthBack, NULL, network, local_addr)); + + delete pthBack; +} + +void test_relay() { + talk_base::Thread* pthMain = talk_base::Thread::Current(); + talk_base::Thread* pthBack = new talk_base::Thread(); + talk_base::Network* network = new talk_base::Network("network", local_addr.ip()); + + RelayServer relay_server(pthBack); + + talk_base::AsyncUDPSocket* int_socket = talk_base::CreateAsyncUDPSocket(pthBack->socketserver()); + assert(int_socket->Bind(relay_int_addr) >= 0); + relay_server.AddInternalSocket(int_socket); + + talk_base::AsyncUDPSocket* ext_socket = talk_base::CreateAsyncUDPSocket(pthBack->socketserver()); + assert(ext_socket->Bind(relay_ext_addr) >= 0); + relay_server.AddExternalSocket(ext_socket); + + std::string username = CreateRandomString(16); + std::string password = CreateRandomString(16); + + RelayPort* rport = + new RelayPort(pthBack, NULL, network, local_addr, username, password, ""); + rport->AddServerAddress(ProtocolAddress(relay_int_addr, PROTO_UDP)); + + test(pthMain, "udp", new UDPPort(pthMain, NULL, network, local_addr), + pthBack, "relay", rport); + + delete pthBack; +} + +const char* NATName(talk_base::NATType type) { + switch (type) { + case talk_base::NAT_OPEN_CONE: return "open cone"; + case talk_base::NAT_ADDR_RESTRICTED: return "addr restricted"; + case talk_base::NAT_PORT_RESTRICTED: return "port restricted"; + case talk_base::NAT_SYMMETRIC: return "symmetric"; + default: + assert(false); + return 0; + } +} + +void test_stun(talk_base::NATType nat_type) { + talk_base::Thread* pthMain = talk_base::Thread::Current(); + talk_base::Thread* pthBack = new talk_base::Thread(); + talk_base::Network* network = new talk_base::Network("network", local_addr.ip()); + + talk_base::NATServer* nat = new talk_base::NATServer( + nat_type, pthMain->socketserver(), nat_addr, + pthMain->socketserver(), nat_addr); + talk_base::NATSocketFactory* nat_factory = + new talk_base::NATSocketFactory(pthMain->socketserver(), nat_addr); + + StunPort* stun_port = + new StunPort(pthMain, nat_factory, network, local_addr, stun_addr); + + talk_base::AsyncUDPSocket* stun_socket = talk_base::CreateAsyncUDPSocket(pthMain->socketserver()); + assert(stun_socket->Bind(stun_addr) >= 0); + StunServer* stun_server = new StunServer(stun_socket); + + char name[256]; + sprintf(name, "stun (%s)", NATName(nat_type)); + + test(pthMain, name, stun_port, + pthBack, "udp", new UDPPort(pthBack, NULL, network, local_addr), + true, nat_type != talk_base::NAT_SYMMETRIC); + + delete stun_server; + delete stun_socket; + delete nat; + delete nat_factory; + delete pthBack; +} + +void test_stun(talk_base::NATType nat1_type, talk_base::NATType nat2_type) { + + talk_base::Thread* pthMain = talk_base::Thread::Current(); + talk_base::Thread* pthBack = new talk_base::Thread(); + talk_base::Network* network = new talk_base::Network("network", local_addr.ip()); + + talk_base::SocketAddress local_addr1(talk_base::LocalHost().networks()[0]->ip(), 0); + talk_base::SocketAddress local_addr2(talk_base::LocalHost().networks()[1]->ip(), 0); + + talk_base::SocketAddress nat1_addr(local_addr1.ip(), talk_base::NAT_SERVER_PORT); + talk_base::SocketAddress nat2_addr(local_addr2.ip(), talk_base::NAT_SERVER_PORT); + + talk_base::SocketAddress stun1_addr(local_addr1.ip(), STUN_SERVER_PORT); + talk_base::SocketAddress stun2_addr(local_addr2.ip(), STUN_SERVER_PORT); + + talk_base::NATServer* nat1 = new talk_base::NATServer( + nat1_type, pthMain->socketserver(), nat1_addr, + pthMain->socketserver(), nat1_addr); + talk_base::NATSocketFactory* nat1_factory = + new talk_base::NATSocketFactory(pthMain->socketserver(), nat1_addr); + + StunPort* stun1_port = + new StunPort(pthMain, nat1_factory, network, local_addr1, stun1_addr); + + talk_base::NATServer* nat2 = new talk_base::NATServer( + nat2_type, pthBack->socketserver(), nat2_addr, + pthBack->socketserver(), nat2_addr); + talk_base::NATSocketFactory* nat2_factory = + new talk_base::NATSocketFactory(pthBack->socketserver(), nat2_addr); + + StunPort* stun2_port = + new StunPort(pthMain, nat2_factory, network, local_addr2, stun2_addr); + + talk_base::AsyncUDPSocket* stun1_socket = talk_base::CreateAsyncUDPSocket(pthMain->socketserver()); + assert(stun1_socket->Bind(stun_addr) >= 0); + StunServer* stun1_server = new StunServer(stun1_socket); + + talk_base::AsyncUDPSocket* stun2_socket = talk_base::CreateAsyncUDPSocket(pthBack->socketserver()); + assert(stun2_socket->Bind(stun2_addr) >= 0); + StunServer* stun2_server = new StunServer(stun2_socket); + + char name1[256], name2[256]; + sprintf(name1, "stun (%s)", NATName(nat1_type)); + sprintf(name2, "stun (%s)", NATName(nat2_type)); + + test(pthMain, name1, stun1_port, + pthBack, name2, stun2_port, + nat2_type == talk_base::NAT_OPEN_CONE, nat1_type != talk_base::NAT_SYMMETRIC); + + delete stun1_server; + delete stun1_socket; + delete stun2_server; + delete stun2_socket; + delete nat1; + delete nat1_factory; + delete nat2; + delete nat2_factory; + delete pthBack; +} + +int main(int argc, char* argv[]) { + InitRandom(NULL, 0); + + test_udp(); + + test_relay(); + + test_stun(talk_base::NAT_OPEN_CONE); + test_stun(talk_base::NAT_ADDR_RESTRICTED); + test_stun(talk_base::NAT_PORT_RESTRICTED); + test_stun(talk_base::NAT_SYMMETRIC); + + test_stun(talk_base::NAT_OPEN_CONE, talk_base::NAT_OPEN_CONE); + test_stun(talk_base::NAT_ADDR_RESTRICTED, talk_base::NAT_OPEN_CONE); + test_stun(talk_base::NAT_PORT_RESTRICTED, talk_base::NAT_OPEN_CONE); + test_stun(talk_base::NAT_SYMMETRIC, talk_base::NAT_OPEN_CONE); + + test_stun(talk_base::NAT_ADDR_RESTRICTED, talk_base::NAT_ADDR_RESTRICTED); + test_stun(talk_base::NAT_PORT_RESTRICTED, talk_base::NAT_ADDR_RESTRICTED); + test_stun(talk_base::NAT_PORT_RESTRICTED, talk_base::NAT_PORT_RESTRICTED); + test_stun(talk_base::NAT_SYMMETRIC, talk_base::NAT_ADDR_RESTRICTED); + + return 0; +} diff --git a/third_party/libjingle/files/talk/p2p/base/portallocator.h b/third_party/libjingle/files/talk/p2p/base/portallocator.h new file mode 100644 index 0000000..50f52cf --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/portallocator.h @@ -0,0 +1,104 @@ +/* + * 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 _PORTALLOCATOR_H_ +#define _PORTALLOCATOR_H_ + +#include "talk/base/sigslot.h" +#include "talk/p2p/base/port.h" +#include <string> +#undef SetPort + +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: + 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) {} + + 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; + } + +protected: + uint32 flags_; + std::string agent_; + talk_base::ProxyInfo proxy_; +}; + +} // namespace cricket + +#endif // _PORTALLOCATOR_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/pseudotcp.cc b/third_party/libjingle/files/talk/p2p/base/pseudotcp.cc new file mode 100644 index 0000000..2f52d65 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/pseudotcp.cc @@ -0,0 +1,1071 @@ +/* + * 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/basicdefs.h" +#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" +#include "talk/p2p/base/pseudotcp.h" + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +// The following logging is for detailed (packet-level) pseudotcp 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::ElapsedTime(); +#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(nTimeout, + talk_base::TimeDiff(m_t_ack + ACK_DELAY, now)); + } + if (m_rto_base) { + nTimeout = talk_base::_min(nTimeout, + talk_base::TimeDiff(m_rto_base + m_rx_rto, now)); + } + if (m_snd_wnd == 0) { + nTimeout = talk_base::_min(nTimeout, talk_base::TimeDiff(m_lastsend + m_rx_rto, now)); + } +#if PSEUDO_KEEPALIVE + if (m_state == TCP_ESTABLISHED) { + nTimeout = talk_base::_min(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(1LU, 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(1LU, 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/third_party/libjingle/files/talk/p2p/base/pseudotcp.h b/third_party/libjingle/files/talk/p2p/base/pseudotcp.h new file mode 100644 index 0000000..cce23e1 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/pseudotcp.h @@ -0,0 +1,182 @@ +/* + * 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 __PSEUDOTCP_H__ +#define __PSEUDOTCP_H__ + +#include <list> +#include "talk/base/basictypes.h" + +namespace cricket { + +////////////////////////////////////////////////////////////////////// +// IPseudoTcpNotify +////////////////////////////////////////////////////////////////////// + +class PseudoTcp; + +class IPseudoTcpNotify { +public: + // 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 nError) = 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 { + uint32 seq, len; + //uint32 tstamp; + uint8 xmit; + bool bCtrl; + + SSegment(uint32 s, uint32 l, bool c) : seq(s), len(l), /*tstamp(0),*/ xmit(0), bCtrl(c) { } + }; + 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 // __PSEUDOTCP_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/rawtransport.cc b/third_party/libjingle/files/talk/p2p/base/rawtransport.cc new file mode 100644 index 0000000..f2e230b --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/rawtransport.cc @@ -0,0 +1,150 @@ +/* + * 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/rawtransport.h" +#include "talk/base/common.h" +#include "talk/p2p/base/constants.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" + +namespace cricket { + +const std::string kNsRawTransport("http://www.google.com/transport/raw-udp"); +const buzz::QName kQnRawTransport(true, kNsRawTransport, "transport"); +const buzz::QName kQnRawChannel(true, kNsRawTransport, "channel"); +const buzz::QName kQnRawBehindSymmetricNat(true, buzz::STR_EMPTY, + "behind-symmetric-nat"); +const buzz::QName kQnRawCanReceiveFromSymmetricNat(true, buzz::STR_EMPTY, + "can-receive-from-symmetric-nat"); + +RawTransport::RawTransport(SessionManager* session_manager) + : Transport(session_manager, kNsRawTransport) { +} + +RawTransport::~RawTransport() { + DestroyAllChannels(); +} + +buzz::XmlElement* RawTransport::CreateTransportOffer() { + buzz::XmlElement* xml = new buzz::XmlElement(kQnRawTransport, true); + + // Assume that we are behind a symmetric NAT. Also note that we can't + // handle the adjustment necessary to talk to someone else who is behind + // a symmetric NAT. + xml->AddAttr(kQnRawBehindSymmetricNat, "true"); + xml->AddAttr(kQnRawCanReceiveFromSymmetricNat, "false"); + + return xml; +} + +buzz::XmlElement* RawTransport::CreateTransportAnswer() { + return new buzz::XmlElement(kQnRawTransport, true); +} + +bool RawTransport::OnTransportOffer(const buzz::XmlElement* elem) { + ASSERT(elem->Name() == kQnRawTransport); + + // If the other side is behind a symmetric NAT then we can't talk to him. + // We also bail if this attribute isn't specified. + if (!elem->HasAttr(kQnRawBehindSymmetricNat) + || elem->Attr(kQnRawBehindSymmetricNat) != "false") { + return false; + } + + // If the other side doesn't explicitly state that he can receive from + // someone behind a symmetric NAT, we bail. + if (!elem->HasAttr(kQnRawCanReceiveFromSymmetricNat) + || elem->Attr(kQnRawCanReceiveFromSymmetricNat) != "true") { + return false; + } + + // We don't support any options, so we ignore them. + return true; +} + +bool RawTransport::OnTransportAnswer(const buzz::XmlElement* elem) { + ASSERT(elem->Name() == kQnRawTransport); + // We don't support any options. We fail if any are given. The other side + // should know from our request that we expected an empty response. + return elem->FirstChild() == NULL; +} + +bool RawTransport::OnTransportMessage(const buzz::XmlElement* msg, + const buzz::XmlElement* stanza) { + ASSERT(msg->Name() == kQnRawTransport); + for (const buzz::XmlElement* elem = msg->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + if (elem->Name() == kQnRawChannel) { + talk_base::SocketAddress addr; + if (!ParseAddress(stanza, elem, &addr)) + return false; + + ForwardChannelMessage(elem->Attr(buzz::QN_NAME), + new buzz::XmlElement(*elem)); + } + } + return true; +} + +bool RawTransport::OnTransportError(const buzz::XmlElement* session_msg, + const buzz::XmlElement* error) { + return true; +} + +bool RawTransport::ParseAddress(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + talk_base::SocketAddress* addr) { + // Make sure the required attributes exist + if (!elem->HasAttr(buzz::QN_NAME) || + !elem->HasAttr(QN_ADDRESS) || + !elem->HasAttr(QN_PORT)) { + return BadRequest(stanza, "channel missing required attribute", NULL); + } + + // Make sure the channel named actually exists. + if (!HasChannel(elem->Attr(buzz::QN_NAME))) + return BadRequest(stanza, "channel named does not exist", NULL); + + // Parse the address. + return Transport::ParseAddress(stanza, elem, addr); +} + +TransportChannelImpl* RawTransport::CreateTransportChannel( + const std::string& name, const std::string &session_type) { + return new RawTransportChannel( + name, session_type, this, session_manager()->port_allocator()); +} + +void RawTransport::DestroyTransportChannel(TransportChannelImpl* channel) { + delete channel; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/rawtransport.h b/third_party/libjingle/files/talk/p2p/base/rawtransport.h new file mode 100644 index 0000000..d273b40 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/rawtransport.h @@ -0,0 +1,84 @@ +/* + * 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_RAWTRANSPORT_H_ +#define _CRICKET_P2P_BASE_RAWTRANSPORT_H_ + +#include "talk/p2p/base/transport.h" + +namespace cricket { + +// Xml names used to name this transport and create our elements +extern const std::string kNsRawTransport; +extern const buzz::QName kQnRawTransport; +extern const buzz::QName kQnRawChannel; +extern const buzz::QName kQnRawNatType; +extern const buzz::QName kQnRawNatTypeAllowed; + +// 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: + RawTransport(SessionManager* session_manager); + virtual ~RawTransport(); + + // Handles the raw transport protocol descriptions, which are trivial. + virtual buzz::XmlElement* CreateTransportOffer(); + virtual buzz::XmlElement* CreateTransportAnswer(); + virtual bool OnTransportOffer(const buzz::XmlElement* elem); + virtual bool OnTransportAnswer(const buzz::XmlElement* elem); + + // Forwards messages containing channel addresses to the appropriate channel. + virtual bool OnTransportMessage(const buzz::XmlElement* msg, + const buzz::XmlElement* stanza); + virtual bool OnTransportError(const buzz::XmlElement* session_msg, + const buzz::XmlElement* error); + + protected: + // Creates and destroys raw channels. + virtual TransportChannelImpl* CreateTransportChannel( + const std::string& name, const std::string &session_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 ParseAddress(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + talk_base::SocketAddress* addr); + + friend class RawTransportChannel; // For ParseAddress. + + DISALLOW_EVIL_CONSTRUCTORS(RawTransport); +}; + +} // namespace cricket + + +#endif // _CRICKET_P2P_BASE_RAWTRANSPORT_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.cc b/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.cc new file mode 100644 index 0000000..13c1886 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.cc @@ -0,0 +1,259 @@ +/* + * 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 "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" + +namespace { + +const int MSG_DESTROY_UNUSED_PORTS = 1; + +} // namespace + +namespace cricket { + +RawTransportChannel::RawTransportChannel( + const std::string &name, const std::string &session_type, RawTransport* transport, + PortAllocator *allocator) + : TransportChannelImpl(name, session_type), raw_transport_(transport), + allocator_(allocator), allocator_session_(NULL), stun_port_(NULL), + relay_port_(NULL), port_(NULL), use_relay_(false) { +} + +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(), session_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::OnChannelMessage(const buzz::XmlElement* msg) { + bool valid = raw_transport_->ParseAddress(NULL, msg, &remote_address_); + ASSERT(valid); + ASSERT(!remote_address_.IsAny()); + set_readable(true); + + // We can write once we have a port and a remote address. + 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(); + raw_transport_->session_manager()->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"); + talk_base::SocketAddress addr = port_->candidates()[0].address(); + + buzz::XmlElement* msg = new buzz::XmlElement(kQnRawChannel); + msg->SetAttr(buzz::QN_NAME, name()); + msg->SetAttr(QN_ADDRESS, addr.IPAsString()); + msg->SetAttr(QN_PORT, addr.PortAsString()); + SignalChannelMessage(this, msg); + + // 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 diff --git a/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.h b/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.h new file mode 100644 index 0000000..9dcbae2 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/rawtransportchannel.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 _CRICKET_P2P_BASE_RAWTRANSPORTCHANNEL_H_ +#define _CRICKET_P2P_BASE_RAWTRANSPORTCHANNEL_H_ + +#include "talk/base/messagequeue.h" +#include "talk/p2p/base/transportchannelimpl.h" +#include "talk/p2p/base/rawtransport.h" +#include "talk/p2p/base/candidate.h" + +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 &session_type, + RawTransport* transport, + 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 SignalChannelMessage 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 OnChannelMessage(const buzz::XmlElement* msg); + + private: + RawTransport* raw_transport_; + 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 // _CRICKET_P2P_BASE_RAWTRANSPORTCHANNEL_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/relayport.cc b/third_party/libjingle/files/talk/p2p/base/relayport.cc new file mode 100644 index 0000000..a8a0527 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/relayport.cc @@ -0,0 +1,634 @@ +/* + * 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/logging.h" +#include "talk/base/asynctcpsocket.h" +#include "talk/base/helpers.h" +#include "talk/p2p/base/relayport.h" +#include <iostream> +#include <cassert> +#ifdef OSX +#include <errno.h> +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +namespace talk_base { +class AsyncTCPSocket; +}; + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 60 * 1000; +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 RETRY_TIMEOUT = 50 * 1000; // ICE says 50 secs + +// Manages a single connection to the relayserver. 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 sigslot::has_slots<> { +public: + RelayEntry(RelayPort* port, const talk_base::SocketAddress& ext_addr, + const talk_base::SocketAddress& local_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; } + + talk_base::AsyncPacketSocket* socket() { return socket_; } + + bool connected() const { return connected_; } + bool locked() const { return locked_; } + + // Returns the last error on the socket of this entry. + int GetError() const { return socket_->GetError(); } + + // 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); + + // 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; } + size_t ServerIndex() const { return server_index_; } + + // Try a different server address + void HandleConnectFailure(); + +private: + RelayPort* port_; + talk_base::SocketAddress ext_addr_, local_addr_; + size_t server_index_; + talk_base::AsyncPacketSocket* socket_; + bool connected_; + bool locked_; + StunRequestManager requests_; + + // Called when a TCP connection is established or fails + void OnSocketConnect(talk_base::AsyncTCPSocket* socket); + void OnSocketClose(talk_base::AsyncTCPSocket* socket, int error); + + // Called when a packet is received on this socket. + void OnReadPacket( + const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + + // 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); + + // 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); + 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_; + uint32 start_time_; +}; + +const std::string RELAY_PORT_TYPE("relay"); + +RelayPort::RelayPort( + talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, const talk_base::SocketAddress& local_addr, + const std::string& username, const std::string& password, + const std::string& magic_cookie) + : Port(thread, RELAY_PORT_TYPE, factory, network), local_addr_(local_addr), + ready_(false), magic_cookie_(magic_cookie), error_(0) { + + entries_.push_back( + new RelayEntry(this, talk_base::SocketAddress(), local_addr_)); + + set_username_fragment(username); + set_password(password); + + if (magic_cookie_.size() == 0) + magic_cookie_.append(STUN_MAGIC_COOKIE_VALUE, 4); +} + +RelayPort::~RelayPort() { + for (unsigned 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 >= 0) && (index < server_addr_.size())) + return &server_addr_[index]; + return 0; +} + +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 connections 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 (unsigned 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, local_addr_); + if (!entries_.empty()) { + // Use the same port to connect to relay server + 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 (int)size; +} + +int RelayPort::SetOption(talk_base::Socket::Option opt, int value) { + int result = 0; + for (unsigned i = 0; i < entries_.size(); ++i) { + if (entries_[i]->socket()->SetOption(opt, value) < 0) { + result = -1; + error_ = entries_[i]->socket()->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); + } +} + +void RelayPort::DisposeSocket(talk_base::AsyncPacketSocket * socket) { + thread_->Dispose(socket); +} + +RelayEntry::RelayEntry(RelayPort* port, + const talk_base::SocketAddress& ext_addr, + const talk_base::SocketAddress& local_addr) + : port_(port), ext_addr_(ext_addr), local_addr_(local_addr), server_index_(0), + socket_(0), connected_(false), locked_(false), requests_(port->thread()) { + + requests_.SignalSendPacket.connect(this, &RelayEntry::OnSendPacket); +} + +RelayEntry::~RelayEntry() { + delete socket_; +} + +void RelayEntry::Connect() { + assert(socket_ == 0); + const ProtocolAddress * ra = port()->ServerAddress(server_index_); + if (!ra) { + LOG(INFO) << "Out of relay server connections"; + return; + } + + LOG(INFO) << "Connecting to relay via " << ProtoToString(ra->proto) << " @ " << ra->address.ToString(); + + socket_ = port_->CreatePacketSocket(ra->proto); + assert(socket_ != 0); + + socket_->SignalReadPacket.connect(this, &RelayEntry::OnReadPacket); + if (socket_->Bind(local_addr_) < 0) + LOG(INFO) << "bind: " << std::strerror(socket_->GetError()); + + for (unsigned i = 0; i < port_->options().size(); ++i) + socket_->SetOption(port_->options()[i].first, port_->options()[i].second); + + if ((ra->proto == PROTO_TCP) || (ra->proto == PROTO_SSLTCP)) { + talk_base::AsyncTCPSocket * tcp + = static_cast<talk_base::AsyncTCPSocket *>(socket_); + tcp->SignalClose.connect(this, &RelayEntry::OnSocketClose); + tcp->SignalConnect.connect(this, &RelayEntry::OnSocketConnect); + tcp->Connect(ra->address); + } else { + requests_.Send(new AllocateRequest(this)); + } +} + +void RelayEntry::OnConnect(const talk_base::SocketAddress& mapped_addr) { + 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(CreateRandomString(16)); + + StunByteStringAttribute* magic_cookie_attr = + StunAttribute::CreateByteString(STUN_ATTR_MAGIC_COOKIE); + magic_cookie_attr->CopyBytes(port_->magic_cookie().c_str(), + (uint16)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(), + (uint16)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, (uint16)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() { + requests_.SendDelayed(new AllocateRequest(this), KEEPALIVE_DELAY); +} + +void RelayEntry::HandleConnectFailure() { + //if (GetMillisecondCount() - start_time_ > RETRY_TIMEOUT) + // return; + //ScheduleKeepAlive(); + + connected_ = false; + port()->DisposeSocket(socket_); + socket_ = 0; + requests_.Clear(); + + server_index_ += 1; + Connect(); +} + +void RelayEntry::OnSocketConnect(talk_base::AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG(INFO) << "relay tcp connected to " << socket->GetRemoteAddress().ToString(); + requests_.Send(new AllocateRequest(this)); +} + +void RelayEntry::OnSocketClose(talk_base::AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + PLOG(LERROR, error) << "relay tcp connect failed"; + HandleConnectFailure(); +} + +void RelayEntry::OnReadPacket(const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + assert(socket == socket_); + //assert(remote_addr == port_->server_addr()); TODO: are we worried about this? + + // 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 (requests_.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); +} + +void RelayEntry::OnSendPacket(const void* data, size_t size, StunRequest* req) { + SendPacket(data, size); +} + +int RelayEntry::SendPacket(const void* data, size_t size) { + const ProtocolAddress * ra = port_->ServerAddress(server_index_); + if (!ra) { + if (socket_) + socket_->SetError(ENOTCONN); + return SOCKET_ERROR; + } + int sent = socket_->SendTo(data, size, ra->address); + if (sent <= 0) { + LOG(LS_VERBOSE) << "sendto: " << std::strerror(socket_->GetError()); + assert(sent < 0); + } + return sent; +} + +AllocateRequest::AllocateRequest(RelayEntry* entry) : entry_(entry) { + start_time_ = talk_base::GetMillisecondCount(); +} + +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(), + (uint16)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(), + (uint16)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); + } + + // 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::GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT) + entry_->ScheduleKeepAlive(); +} + +void AllocateRequest::OnTimeout() { + LOG(INFO) << "Allocate request timed out"; + entry_->HandleConnectFailure(); +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/relayport.h b/third_party/libjingle/files/talk/p2p/base/relayport.h new file mode 100644 index 0000000..5691a6c --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/relayport.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 __RELAYPORT_H__ +#define __RELAYPORT_H__ + +#include "talk/p2p/base/port.h" +#include "talk/p2p/base/stunrequest.h" +#include <vector> + +namespace cricket { + +extern const std::string RELAY_PORT_TYPE; +class RelayEntry; + +// Communicates using an allocated port on the relay server. +class RelayPort : public Port { +public: + RelayPort( + talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network*, const talk_base::SocketAddress& local_addr, + const std::string& username, const std::string& password, + const std::string& magic_cookie); + virtual ~RelayPort(); + + void AddServerAddress(const ProtocolAddress& addr); + void AddExternalAddress(const ProtocolAddress& addr); + + typedef std::pair<talk_base::Socket::Option, int> OptionValue; + 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; + + void DisposeSocket(talk_base::AsyncPacketSocket * socket); + +protected: + 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; + + talk_base::SocketAddress local_addr_; + std::deque<ProtocolAddress> server_addr_; + bool ready_; + std::vector<RelayEntry*> entries_; + std::vector<OptionValue> options_; + std::string magic_cookie_; + int error_; +}; + +} // namespace cricket + +#endif // __RELAYPORT_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/relayserver.cc b/third_party/libjingle/files/talk/p2p/base/relayserver.cc new file mode 100644 index 0000000..1f4a979 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/relayserver.cc @@ -0,0 +1,671 @@ +/* + * 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" +#include "talk/base/helpers.h" +#include <algorithm> +#include <cassert> +#include <cstring> +#include <iostream> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +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; + +// 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 < int(size)) { + std::cerr << "SendTo wrote only " << result << " of " << int(size) + << " bytes" << std::endl; + } else if (result < 0) { + std::cerr << "SendTo: " << std::strerror(errno) << std::endl; + } +} + +// 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() { + for (unsigned i = 0; i < internal_sockets_.size(); i++) + delete internal_sockets_[i]; + for (unsigned i = 0; i < external_sockets_.size(); i++) + delete external_sockets_[i]; +} + +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::OnInternalPacket( + const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + talk_base::SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + 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()) { + std::cerr << "Dropping packet: connection not locked" << std::endl; + 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. + //std::cerr << "Dropping packet: no external connection" << std::endl; + } +} + +void RelayServer::OnExternalPacket( + const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + + // Get the address of the connection we just received on. + talk_base::SocketAddressPair ap(remote_addr, socket->GetLocalAddress()); + 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); + 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 = talk_base::ByteBuffer(bytes, size); + if (!msg.Read(&buf)) { + std::cerr << "Dropping packet: first packet not STUN" << std::endl; + return; + } + + // The initial packet should have a username (which identifies the binding). + const StunByteStringAttribute* username_attr = + msg.GetByteString(STUN_ATTR_USERNAME); + if (!username_attr) { + std::cerr << "Dropping packet: no username" << std::endl; + return; + } + + uint32 length = talk_base::_min(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()) { + // TODO: Turn this back on. This is the sign of a client bug. + //std::cerr << "Dropping packet: no binding with username" << std::endl; + 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); + 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 = talk_base::ByteBuffer(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_) { + std::cout << "Added new binding: " << bindings_.size() << " total" + << std::endl; + } + } + + // 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(); + talk_base::SocketAddress ext_addr = external_sockets_[index]->GetLocalAddress(); + + 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]; + talk_base::SocketAddressPair ap(ext_addr, socket->GetLocalAddress()); + 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 != 0)) { + 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_) { + std::cout << "Removed a binding: " << bindings_.size() << " remaining" + << std::endl; + } +} + +void RelayServer::OnTimeout(RelayServerBinding* binding) { + // This call will result in all of the necessary clean-up. + delete binding; +} + +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_) { + SignalTimeout(this); + } else { + server_->thread()->PostDelayed(lifetime_, this, MSG_LIFETIME_TIMER); + } + + } else { + assert(false); + } +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/relayserver.h b/third_party/libjingle/files/talk/p2p/base/relayserver.h new file mode 100644 index 0000000..b99c632 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/relayserver.h @@ -0,0 +1,215 @@ +/* + * 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 __RELAYSERVER_H__ +#define __RELAYSERVER_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/base/socketaddresspair.h" +#include "talk/base/thread.h" +#include "talk/base/time.h" +#include "talk/p2p/base/stun.h" + +#include <string> +#include <vector> +#include <map> + +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 sigslot::has_slots<> { +public: + // Creates a server, which will use this thread to post messages to itself. + 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); + +private: + typedef std::vector<talk_base::AsyncPacketSocket*> SocketList; + 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_; + BindingMap bindings_; + ConnectionMap connections_; + + // Called when a packet is received by the server on one of its sockets. + void OnInternalPacket( + const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + void OnExternalPacket( + const char* bytes, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* 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); + + // Called when the timer for checking lifetime times out. + void OnTimeout(RelayServerBinding* binding); + + 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 // __RELAYSERVER_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/relayserver_main.cc b/third_party/libjingle/files/talk/p2p/base/relayserver_main.cc new file mode 100644 index 0000000..cdd5fff --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/relayserver_main.cc @@ -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. + */ + +#include <cassert> +#include <iostream> +#include "talk/base/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/relayserver.h" + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char **argv) { + if (argc != 1) { + std::cerr << "usage: relayserver" << std::endl; + return 1; + } + + assert(talk_base::LocalHost().networks().size() >= 2); + talk_base::SocketAddress int_addr(talk_base::LocalHost().networks()[1]->ip(), 5000); + talk_base::SocketAddress ext_addr(talk_base::LocalHost().networks()[1]->ip(), 5001); + + talk_base::Thread *pthMain = talk_base::Thread::Current(); + + talk_base::AsyncUDPSocket* int_socket = talk_base::CreateAsyncUDPSocket(pthMain->socketserver()); + if (int_socket->Bind(int_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + talk_base::AsyncUDPSocket* ext_socket = talk_base::CreateAsyncUDPSocket(pthMain->socketserver()); + if (ext_socket->Bind(ext_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + RelayServer server(pthMain); + server.AddInternalSocket(int_socket); + server.AddExternalSocket(ext_socket); + + 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/third_party/libjingle/files/talk/p2p/base/session.cc b/third_party/libjingle/files/talk/p2p/base/session.cc new file mode 100644 index 0000000..1ea0dc3 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/session.cc @@ -0,0 +1,1029 @@ +/* + * 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/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/constants.h" + +namespace { + +const uint32 MSG_TIMEOUT = 1; +const uint32 MSG_ERROR = 2; +const uint32 MSG_STATE = 3; + +// This will be initialized at run time to hold the list of default transports. +std::string* gDefaultTransports = NULL; +size_t gNumDefaultTransports = 0; + +} // namespace + +namespace cricket { + +Session::Session(SessionManager *session_manager, const std::string& name, + const SessionID& id, const std::string& session_type, + SessionClient* client) { + ASSERT(session_manager->signaling_thread()->IsCurrent()); + ASSERT(client != NULL); + session_manager_ = session_manager; + name_ = name; + id_ = id; + session_type_ = session_type; + client_ = client; + error_ = ERROR_NONE; + state_ = STATE_INIT; + initiator_ = false; + description_ = NULL; + remote_description_ = NULL; + transport_ = NULL; + compatibility_mode_ = false; +} + +Session::~Session() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + ASSERT(state_ != STATE_DEINIT); + state_ = STATE_DEINIT; + SignalState(this, state_); + + delete description_; + delete remote_description_; + + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + iter->second->SignalDestroyed(iter->second); + delete iter->second; + } + + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + delete *iter; + } + + delete transport_; +} + +bool Session::Initiate(const std::string &to, + std::vector<buzz::XmlElement*>* extra_xml, + const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only from STATE_INIT + if (state_ != STATE_INIT) + return false; + + // Setup for signaling. + remote_name_ = to; + initiator_ = true; + description_ = description; + + // Make sure we have transports to negotiate. + CreateTransports(); + + // Send the initiate message, including the application and transport offers. + XmlElements elems; + elems.push_back(client_->TranslateSessionDescription(description)); + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + buzz::XmlElement* elem = (*iter)->CreateTransportOffer(); + elems.push_back(elem); + } + + if (extra_xml != NULL) { + std::vector<buzz::XmlElement*>::iterator iter = extra_xml->begin(); + for (std::vector<buzz::XmlElement*>::iterator iter = extra_xml->begin(); + iter != extra_xml->end(); + ++iter) { + elems.push_back(new buzz::XmlElement(**iter)); + } + } + + SendSessionMessage("initiate", elems); + + SetState(Session::STATE_SENTINITIATE); + + // We speculatively start attempting connection of the P2P transports. + ConnectDefaultTransportChannels(true); + return true; +} + +void Session::ConnectDefaultTransportChannels(bool create) { + Transport* transport = GetTransport(kNsP2pTransport); + if (transport) { + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + ASSERT(create != transport->HasChannel(iter->first)); + if (create) { + transport->CreateChannel(iter->first, session_type()); + } + } + transport->ConnectChannels(); + } +} + +void Session::CreateDefaultTransportChannel(const std::string& name) { + // This method is only relevant when we have created the default transport + // but not received a transport-accept. + ASSERT(transport_ == NULL); + ASSERT(state_ == STATE_SENTINITIATE); + + Transport* p2p_transport = GetTransport(kNsP2pTransport); + if (p2p_transport) { + ASSERT(!p2p_transport->HasChannel(name)); + p2p_transport->CreateChannel(name, session_type()); + } +} + +bool Session::Accept(const SessionDescription *description) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Only if just received initiate + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + // Setup for signaling. + initiator_ = false; + description_ = description; + + // If we haven't selected a transport, wait for ChooseTransport to complete + if (transport_ == NULL) + return true; + + // Send the accept message. + XmlElements elems; + elems.push_back(client_->TranslateSessionDescription(description_)); + SendSessionMessage("accept", elems); + SetState(Session::STATE_SENTACCEPT); + + return true; +} + +bool Session::Reject() { + ASSERT(session_manager_->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; + + // Send the reject message. + SendSessionMessage("reject", XmlElements()); + SetState(STATE_SENTREJECT); + + return true; +} + +bool Session::Redirect(const std::string & target) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Redirect is sent in response to an initiate or modify, to redirect the + // request + if (state_ != STATE_RECEIVEDINITIATE) + return false; + + // Setup for signaling. + initiator_ = false; + + // Send a redirect message to the given target. We include an element that + // names the redirector (us), which may be useful to the other side. + + buzz::XmlElement* target_elem = new buzz::XmlElement(QN_REDIRECT_TARGET); + target_elem->AddAttr(buzz::QN_NAME, target); + + buzz::XmlElement* cookie = new buzz::XmlElement(QN_REDIRECT_COOKIE); + buzz::XmlElement* regarding = new buzz::XmlElement(QN_REDIRECT_REGARDING); + regarding->AddAttr(buzz::QN_NAME, name_); + cookie->AddElement(regarding); + + XmlElements elems; + elems.push_back(target_elem); + elems.push_back(cookie); + SendSessionMessage("redirect", elems); + + // A redirect puts us in the same state as reject. It just sends a different + // kind of reject message, if you like. + SetState(STATE_SENTREDIRECT); + + return true; +} + +bool Session::Terminate() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Either side can terminate, at any time. + switch (state_) { + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + return false; + + case STATE_SENTREDIRECT: + // We must not send terminate if we redirect. + break; + + 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: + SendSessionMessage("terminate", XmlElements()); + break; + } + + SetState(STATE_SENTTERMINATE); + return true; +} + +void Session::SendInfoMessage(const XmlElements& elems) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + SendSessionMessage("info", elems); +} + +void Session::SetPotentialTransports(const std::string names[], size_t length) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + for (size_t i = 0; i < length; ++i) { + Transport* transport = NULL; + if (names[i] == kNsP2pTransport) { + transport = new P2PTransport(session_manager_); + } else { + ASSERT(false); + } + + if (transport) { + ASSERT(transport->name() == names[i]); + potential_transports_.push_back(transport); + transport->SignalConnecting.connect( + this, &Session::OnTransportConnecting); + transport->SignalWritableState.connect( + this, &Session::OnTransportWritable); + transport->SignalRequestSignaling.connect( + this, &Session::OnTransportRequestSignaling); + transport->SignalTransportMessage.connect( + this, &Session::OnTransportSendMessage); + transport->SignalTransportError.connect( + this, &Session::OnTransportSendError); + transport->SignalChannelGone.connect( + this, &Session::OnTransportChannelGone); + } + } +} + +Transport* Session::GetTransport(const std::string& name) { + if (transport_ != NULL) { + if (name == transport_->name()) + return transport_; + } else { + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + if (name == (*iter)->name()) + return *iter; + } + } + return NULL; +} + +TransportChannel* Session::CreateChannel(const std::string& name) { + //ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(channels_.find(name) == channels_.end()); + TransportChannelProxy* channel = new TransportChannelProxy(name, session_type_); + channels_[name] = channel; + if (transport_) { + ASSERT(!transport_->HasChannel(name)); + channel->SetImplementation(transport_->CreateChannel(name, session_type_)); + } else if (state_ == STATE_SENTINITIATE) { + // In this case, we have already speculatively created the default + // transport. We should create this channel as well so that it may begin + // early connection. + CreateDefaultTransportChannel(name); + } + return channel; +} + +TransportChannel* Session::GetChannel(const std::string& name) { + ChannelMap::iterator iter = channels_.find(name); + return (iter != channels_.end()) ? iter->second : NULL; +} + +void Session::DestroyChannel(TransportChannel* channel) { + ChannelMap::iterator iter = channels_.find(channel->name()); + ASSERT(iter != channels_.end()); + ASSERT(channel == iter->second); + channels_.erase(iter); + channel->SignalDestroyed(channel); + delete channel; +} + +TransportChannelImpl* Session::GetImplementation(TransportChannel* channel) { + ChannelMap::iterator iter = channels_.find(channel->name()); + return (iter != channels_.end()) ? iter->second->impl() : NULL; +} + +void Session::CreateTransports() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT((state_ == STATE_INIT) + || (state_ == STATE_RECEIVEDINITIATE)); + if (potential_transports_.empty()) { + if (gDefaultTransports == NULL) { + gNumDefaultTransports = 1; + gDefaultTransports = new std::string[1]; + gDefaultTransports[0] = kNsP2pTransport; + } + SetPotentialTransports(gDefaultTransports, gNumDefaultTransports); + } +} + +bool Session::ChooseTransport(const buzz::XmlElement* stanza) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(state_ == STATE_RECEIVEDINITIATE); + ASSERT(transport_ == NULL); + + // Make sure we have decided on our own transports. + CreateTransports(); + + // Retrieve the session message. + const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); + ASSERT(session != NULL); + + // Try the offered transports until we find one that we support. + bool found_offer = false; + const buzz::XmlElement* elem = session->FirstElement(); + while (elem) { + if (elem->Name().LocalPart() == "transport") { + found_offer = true; + Transport* transport = GetTransport(elem->Name().Namespace()); + if (transport && transport->OnTransportOffer(elem)) { + SetTransport(transport); + break; + } + } + elem = elem->NextElement(); + } + + // If the offer did not include any transports, then we are talking to an + // old client. In that case, we turn on compatibility mode, and we assume + // an offer containing just P2P, which is all that old clients support. + if (!found_offer) { + compatibility_mode_ = true; + + Transport* transport = GetTransport(kNsP2pTransport); + ASSERT(transport != NULL); + + scoped_ptr<buzz::XmlElement> transport_offer( + new buzz::XmlElement(kQnP2pTransport, true)); + bool valid = transport->OnTransportOffer(transport_offer.get()); + ASSERT(valid); + if (valid) + SetTransport(transport); + } + + if (!transport_) { + SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ACCEPTABLE, "modify", + "no supported transport in offer", NULL); + return false; + } + + // Get the description of the transport we picked. + buzz::XmlElement* answer = transport_->CreateTransportAnswer(); + ASSERT(answer->Name() == buzz::QName(transport_->name(), "transport")); + + // Send a transport-accept message telling the other side our decision, + // unless this is an old client that is not expecting one. + if (!compatibility_mode_) { + XmlElements elems; + elems.push_back(answer); + SendSessionMessage("transport-accept", elems); + } + + // If the user wants to accept, allow that now + if (description_) { + Accept(description_); + } + + return true; +} + +void Session::SetTransport(Transport* transport) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(transport_ == NULL); + transport_ = transport; + + // Drop the transports that were not selected. + bool found = false; + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + if (*iter == transport_) { + found = true; + } else { + delete *iter; + } + } + potential_transports_.clear(); + + // We require the selected transport to be one of the potential transports + ASSERT(found); + + // Create implementations for all of the channels if they don't exist. + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + TransportChannelProxy* channel = iter->second; + TransportChannelImpl* impl = transport_->GetChannel(channel->name()); + if (impl == NULL) + impl = transport_->CreateChannel(channel->name(), session_type()); + ASSERT(impl != NULL); + channel->SetImplementation(impl); + } + + // Have this transport start connecting if it is not already. + // (We speculatively connect the most common transport right away.) + transport_->ConnectChannels(); +} + +void Session::SetState(State state) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (state != state_) { + state_ = state; + SignalState(this, state_); + session_manager_->signaling_thread()->Post(this, MSG_STATE); + } +} + +void Session::SetError(Error error) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + if (error != error_) { + error_ = error; + SignalError(this, error); + if (error_ != ERROR_NONE) + session_manager_->signaling_thread()->Post(this, MSG_ERROR); + } +} + +void Session::OnSignalingReady() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + // Forward this to every transport. Those that did not request it should + // ignore this call. + if (transport_ != NULL) { + transport_->OnSignalingReady(); + } else { + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + (*iter)->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(session_manager_->signaling_thread()->IsCurrent()); + ASSERT((NULL == transport_) || (transport == transport_)); + + // 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. + + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + if (transport->HasChannels() && !transport->writable()) { + session_manager_->signaling_thread()->PostDelayed( + session_manager_->session_timeout() * 1000, this, MSG_TIMEOUT); + } +} + +void Session::OnTransportRequestSignaling(Transport* transport) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + SignalRequestSignaling(this); +} + +void Session::OnTransportSendMessage(Transport* transport, + const XmlElements& elems) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + for (size_t i = 0; i < elems.size(); ++i) + ASSERT(elems[i]->Name() == buzz::QName(transport->name(), "transport")); + + if (compatibility_mode_) { + // In backward compatibility mode, we send a candidates message. + XmlElements candidates; + for (size_t i = 0; i < elems.size(); ++i) { + for (const buzz::XmlElement* elem = elems[i]->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + ASSERT(elem->Name() == kQnP2pCandidate); + + // Convert this candidate to an old style candidate (namespace change) + buzz::XmlElement* legacy_candidate = new buzz::XmlElement(*elem); + legacy_candidate->SetName(kQnLegacyCandidate); + candidates.push_back(legacy_candidate); + } + delete elems[i]; + } + + SendSessionMessage("candidates", candidates); + } else { + // If we haven't finished negotiation, then we may later discover that we + // need compatibility mode, in which case, we will need to re-send these. + if ((transport_ == NULL) && (transport->name() == kNsP2pTransport)) { + for (size_t i = 0; i < elems.size(); ++i) + candidates_.push_back(new buzz::XmlElement(*elems[i])); + } + + SendSessionMessage("transport-info", elems); + } +} + +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(session_manager_->signaling_thread()->IsCurrent()); + SignalErrorMessage(this, stanza, name, type, text, extra_info); +} + +void Session::OnTransportChannelGone(Transport* transport, + const std::string& name) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + SignalChannelGone(this, name); +} + +void Session::SendSessionMessage( + const std::string& type, const std::vector<buzz::XmlElement*>& elems) { + scoped_ptr<buzz::XmlElement> iq(new buzz::XmlElement(buzz::QN_IQ)); + iq->SetAttr(buzz::QN_TO, remote_name_); + iq->SetAttr(buzz::QN_TYPE, buzz::STR_SET); + + buzz::XmlElement *session = new buzz::XmlElement(QN_SESSION, true); + session->AddAttr(buzz::QN_TYPE, type); + session->AddAttr(buzz::QN_ID, id_.id_str()); + session->AddAttr(QN_INITIATOR, id_.initiator()); + + for (size_t i = 0; i < elems.size(); ++i) + session->AddElement(elems[i]); + + iq->AddElement(session); + SignalOutgoingMessage(this, iq.get()); +} + +void Session::SendAcknowledgementMessage(const buzz::XmlElement* stanza) { + 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()); +} + +void Session::OnIncomingMessage(const buzz::XmlElement* stanza) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(stanza->Name() == buzz::QN_IQ); + buzz::Jid remote(remote_name_); + buzz::Jid from(stanza->Attr(buzz::QN_FROM)); + ASSERT(state_ == STATE_INIT || from == remote); + + const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); + ASSERT(session != NULL); + + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) { + ASSERT(false); + return; + } + + ASSERT(session->HasAttr(buzz::QN_TYPE)); + std::string type = session->Attr(buzz::QN_TYPE); + + bool valid = false; + + if (type == "initiate") { + valid = OnInitiateMessage(stanza, session); + } else if (type == "accept") { + valid = OnAcceptMessage(stanza, session); + } else if (type == "reject") { + valid = OnRejectMessage(stanza, session); + } else if (type == "redirect") { + valid = OnRedirectMessage(stanza, session); + } else if (type == "info") { + valid = OnInfoMessage(stanza, session); + } else if (type == "transport-accept") { + valid = OnTransportAcceptMessage(stanza, session); + } else if (type == "transport-info") { + valid = OnTransportInfoMessage(stanza, session); + } else if (type == "terminate") { + valid = OnTerminateMessage(stanza, session); + } else if (type == "candidates") { + // This is provided for backward compatibility. + // TODO: Remove this once old candidates are gone. + valid = OnCandidatesMessage(stanza, session); + } else { + SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + "unknown session message type", NULL); + } + + // If the message was not valid, we should have sent back an error above. + // If it was valid, then we send an acknowledgement here. + if (valid) + SendAcknowledgementMessage(stanza); +} + +void Session::OnFailedSend(const buzz::XmlElement* orig_stanza, + const buzz::XmlElement* error_stanza) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + + const buzz::XmlElement* orig_session = orig_stanza->FirstNamed(QN_SESSION); + ASSERT(orig_session != NULL); + + std::string error_type = "cancel"; + + const buzz::XmlElement* error = error_stanza->FirstNamed(buzz::QN_ERROR); + ASSERT(error != NULL); + if (error) { + ASSERT(error->HasAttr(buzz::QN_TYPE)); + error_type = error->Attr(buzz::QN_TYPE); + + LOG(LERROR) << "Session error:\n" << error->Str() << "\n" + << "in response to:\n" << orig_session->Str(); + } + + bool fatal_error = false; + + ASSERT(orig_session->HasAttr(buzz::QN_TYPE)); + if ((orig_session->Attr(buzz::QN_TYPE) == "transport-info") + || (orig_session->Attr(buzz::QN_TYPE) == "candidates")) { + // 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. + for (const buzz::XmlElement* elem = error->FirstElement(); + NULL != elem; elem = elem->NextElement()) { + if (Transport* transport = GetTransport(elem->Name().Namespace())) { + if (!transport->OnTransportError(orig_session, elem)) { + fatal_error = true; + break; + } + } + } + } 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. + fatal_error = true; + } + + if (fatal_error) { + SetError(ERROR_RESPONSE); + } +} + +bool Session::OnInitiateMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + if (!CheckState(stanza, STATE_INIT)) + return false; + if (!FindRemoteSessionDescription(stanza, session)) + return false; + + initiator_ = false; + remote_name_ = stanza->Attr(buzz::QN_FROM); + SetState(STATE_RECEIVEDINITIATE); + return true; +} + +bool Session::OnAcceptMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + if (!CheckState(stanza, STATE_SENTINITIATE)) + return false; + if (!FindRemoteSessionDescription(stanza, session)) + return false; + + SetState(STATE_RECEIVEDACCEPT); + return true; +} + +bool Session::OnRejectMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + if (!CheckState(stanza, STATE_SENTINITIATE)) + return false; + + SetState(STATE_RECEIVEDREJECT); + return true; +} + +bool Session::OnRedirectMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + if (!CheckState(stanza, STATE_SENTINITIATE)) + return false; + + const buzz::XmlElement *redirect_target; + if (!FindRequiredElement(stanza, session, QN_REDIRECT_TARGET, + &redirect_target)) + return false; + + if (!FindRequiredAttribute(stanza, redirect_target, buzz::QN_NAME, + &remote_name_)) + return false; + + const buzz::XmlElement* redirect_cookie = + session->FirstNamed(QN_REDIRECT_COOKIE); + + XmlElements elems; + elems.push_back(client_->TranslateSessionDescription(description_)); + if (redirect_cookie) + elems.push_back(new buzz::XmlElement(*redirect_cookie)); + SendSessionMessage("initiate", elems); + + // Clear the connection timeout (if any). We will start the connection + // timer from scratch when SignalConnecting fires. + session_manager_->signaling_thread()->Clear(this, MSG_TIMEOUT); + + // Reset all of the sockets back into the initial state. + for (TransportList::iterator iter = potential_transports_.begin(); + iter != potential_transports_.end(); + ++iter) { + (*iter)->ResetChannels(); + } + + ConnectDefaultTransportChannels(false); + return true; +} + +bool Session::OnInfoMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + XmlElements elems; + for (const buzz::XmlElement* elem = session->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + elems.push_back(new buzz::XmlElement(*elem)); + } + + SignalInfoMessage(this, elems); + return true; +} + +bool Session::OnTransportAcceptMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + if (!CheckState(stanza, STATE_SENTINITIATE)) + return false; + + Transport* transport = NULL; + const buzz::XmlElement* transport_elem = NULL; + + for(const buzz::XmlElement* elem = session->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + if (elem->Name().LocalPart() == "transport") { + Transport* transport = GetTransport(elem->Name().Namespace()); + if (transport) { + if (transport_elem) { // trying to accept two transport? + SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, + "modify", "transport-accept has two answers", + NULL); + return false; + } + + transport_elem = elem; + if (!transport->OnTransportAnswer(transport_elem)) { + SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, + "modify", "transport-accept is not acceptable", + NULL); + return false; + } + SetTransport(transport); + } + } + } + + if (!transport_elem) { + SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ALLOWED, "modify", + "no supported transport in answer", NULL); + return false; + } + + // If we discovered that we need compatibility mode and we have sent some + // candidates already (using transport-info), then we need to re-send them + // using the candidates message. + if (compatibility_mode_ && (candidates_.size() > 0)) { + ASSERT(transport_ != NULL); + ASSERT(transport_->name() == kNsP2pTransport); + OnTransportSendMessage(transport_, candidates_); + } else { + for (size_t i = 0; i < candidates_.size(); ++i) + delete candidates_[i]; + } + candidates_.clear(); + + return true; +} + +bool Session::OnTransportInfoMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + for(const buzz::XmlElement* elem = session->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + if (elem->Name().LocalPart() == "transport") { + Transport* transport = GetTransport(elem->Name().Namespace()); + if (transport) { + if (!transport->OnTransportMessage(elem, stanza)) + return false; + } + } + } + return true; +} + +bool Session::OnTerminateMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + for (const buzz::XmlElement *elem = session->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + // elem->Name().LocalPart() is the reason for termination + SignalReceivedTerminateReason(this, elem->Name().LocalPart()); + // elem->FirstElement() might contain a debug string for termination + const buzz::XmlElement *debugElement = elem->FirstElement(); + if (debugElement != NULL) { + LOG(LS_VERBOSE) << "Received error on call: " + << debugElement->Name().LocalPart(); + } + } + SetState(STATE_RECEIVEDTERMINATE); + return true; +} + +bool Session::OnCandidatesMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + // If we don't have a transport, then this is the first candidates message. + // We first create a fake transport-accept message in order to finish the + // negotiation and create a transport. + if (!transport_) { + compatibility_mode_ = true; + + scoped_ptr<buzz::XmlElement> transport_accept( + new buzz::XmlElement(QN_SESSION)); + transport_accept->SetAttr(buzz::QN_TYPE, "transport-accept"); + + buzz::XmlElement* transport_offer = + new buzz::XmlElement(kQnP2pTransport, true); + transport_accept->AddElement(transport_offer); + + // It is okay to pass the original stanza here. That is only used if we + // send an error message. Normal processing looks only at transport_accept. + bool valid = OnTransportAcceptMessage(stanza, transport_accept.get()); + ASSERT(valid); + } + + ASSERT(transport_ != NULL); + ASSERT(transport_->name() == kNsP2pTransport); + + // Wrap the candidates in a transport element as they would appear in a + // transport-info message and send this to the transport. + scoped_ptr<buzz::XmlElement> transport_info( + new buzz::XmlElement(kQnP2pTransport, true)); + for (const buzz::XmlElement* elem = session->FirstNamed(kQnLegacyCandidate); + elem != NULL; + elem = elem->NextNamed(kQnLegacyCandidate)) { + buzz::XmlElement* new_candidate = new buzz::XmlElement(*elem); + new_candidate->SetName(kQnP2pCandidate); + transport_info->AddElement(new_candidate); + } + return transport_->OnTransportMessage(transport_info.get(), stanza); +} + +bool Session::CheckState(const buzz::XmlElement* stanza, State state) { + ASSERT(state_ == state); + if (state_ != state) { + SignalErrorMessage(this, stanza, buzz::QN_STANZA_NOT_ALLOWED, "modify", + "message not allowed in current state", NULL); + return false; + } + return true; +} + +bool Session::FindRequiredElement(const buzz::XmlElement* stanza, + const buzz::XmlElement* parent, + const buzz::QName& name, + const buzz::XmlElement** elem) { + *elem = parent->FirstNamed(name); + if (*elem == NULL) { + std::string text; + text += "element '" + parent->Name().Merged() + + "' missing required child '" + name.Merged() + "'"; + SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + text, NULL); + return false; + } + return true; +} + +bool Session::FindRemoteSessionDescription(const buzz::XmlElement* stanza, + const buzz::XmlElement* session) { + buzz::QName qn_session(session_type_, "description"); + const buzz::XmlElement* desc; + if (!FindRequiredElement(stanza, session, qn_session, &desc)) + return false; + remote_description_ = client_->CreateSessionDescription(desc); + return true; +} + +bool Session::FindRequiredAttribute(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + const buzz::QName& name, + std::string* value) { + if (!elem->HasAttr(name)) { + std::string text; + text += "element '" + elem->Name().Merged() + + "' missing required attribute '" + name.Merged() + "'"; + SignalErrorMessage(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + text, NULL); + return false; + } else { + *value = elem->Attr(name); + return true; + } +} + +void Session::OnMessage(talk_base::Message *pmsg) { + switch(pmsg->message_id) { + case MSG_TIMEOUT: + // Session timeout has occured. + SetError(ERROR_TIME); + break; + + case MSG_ERROR: + // Any of the defined errors is most likely fatal. + Terminate(); + break; + + case MSG_STATE: + switch (state_) { + case STATE_SENTACCEPT: + case STATE_RECEIVEDACCEPT: + SetState(STATE_INPROGRESS); + ASSERT(transport_ != NULL); + break; + + case STATE_SENTREJECT: + case STATE_SENTREDIRECT: + case STATE_RECEIVEDREJECT: + Terminate(); + break; + + case STATE_SENTTERMINATE: + case STATE_RECEIVEDTERMINATE: + session_manager_->DestroySession(this); + break; + + default: + // Explicitly ignoring some states here. + break; + } + break; + } +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/session.h b/third_party/libjingle/files/talk/p2p/base/session.h new file mode 100644 index 0000000..5146ea0 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/session.h @@ -0,0 +1,357 @@ +/* + * 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 _SESSION_H_ +#define _SESSION_H_ + +#include "talk/base/socketaddress.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/sessionclient.h" +#include "talk/p2p/base/sessionid.h" +#include "talk/p2p/base/port.h" +#include "talk/xmllite/xmlelement.h" +#include <string> + +namespace cricket { + +class SessionManager; +class Transport; +class TransportChannel; +class TransportChannelProxy; +class TransportChannelImpl; + +// A specific Session created by the SessionManager. A Session manages +// signaling for session setup and tear down. This setup 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. +class Session : public talk_base::MessageHandler, public sigslot::has_slots<> { + 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, // no response to signaling + ERROR_RESPONSE, // error during signaling + ERROR_NETWORK, // network error, could not allocate network resources + }; + + // Returns the manager that created and owns this session. + SessionManager* session_manager() const { return session_manager_; } + + // Returns the XML namespace identifying the type of this session. + const std::string& session_type() const { return session_type_; } + + // Returns the client that is handling the application data of this session. + SessionClient* client() const { return client_; } + + // Returns the JID this client. + const std::string &name() const { return name_; } + + // Returns the JID of the other peer in this session. + const std::string &remote_name() const { return remote_name_; } + + // Indicates whether we initiated this session. + bool initiator() const { return initiator_; } + + // Holds the ID of this session, which should be unique across the world. + const SessionID& id() const { return id_; } + + // Returns the applicication-level description given by our client. This + // will be null until Initiate or Accept. + const SessionDescription *description() const { return description_; } + + // Returns the applicication-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_; + } + + // 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<Session *, State> SignalState; + + // Fired whenever we receive a terminate message along with a reason + sigslot::signal2<Session *, const std::string &> SignalReceivedTerminateReason; + + // 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<Session *, Error> SignalError; + + // Returns the transport that has been negotiated or NULL if negotiation is + // still in progress. + Transport* transport() const { return transport_; } + + // When a session was created by us, we are the initiator, and we send the + // initiate message when this method is invoked. The extra_xml parameter is + // a list of elements that will get inserted inside <Session> ... </Session> + bool Initiate(const std::string &to, std::vector<buzz::XmlElement*>* extra_xml, + const SessionDescription *description); + + // When we receive a session initiation from another client, we create a + // session in the RECEIVEDINITIATE state. We respond by accepting, + // rejecting, or redirecting the session somewhere else. + bool Accept(const SessionDescription *description); + bool Reject(); + bool Redirect(const std::string& target); + + // At any time, we may terminate an outstanding session. + bool Terminate(); + + // 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. + typedef std::vector<buzz::XmlElement*> XmlElements; + void SendInfoMessage(const XmlElements& elems); + sigslot::signal2<Session*, const XmlElements&> SignalInfoMessage; + + // Controls the set of transports that will be allowed for this session. If + // we are initiating, then this list will be used to construct the transports + // that we will offer to the other side. In that case, the order of the + // transport names indicates our preference (first has highest preference). + // If we are receiving, then this list indicates the set of transports that + // we will allow. We will choose the first transport in the offered list + // (1) whose name appears in the given list and (2) that can accept the offer + // provided (which may include parameters particular to the transport). + // + // If this function is not called (or if it is called with a NULL array), + // then we will use a default set of transports. + void SetPotentialTransports(const std::string names[], size_t length); + + // Once transports have been created (by SetTransports), this function will + // return the transport with the given name or NULL if none was created. + // Once a particular transport has been chosen, only that transport will be + // returned. + Transport* GetTransport(const std::string& name); + + // Creates a new channel with the given name. This method may be called + // immediately after creating the session. However, the actual + // implementation may not be fixed until transport negotiation completes. + TransportChannel* CreateChannel(const std::string& name); + + // Returns the channel with the given name. + TransportChannel* GetChannel(const std::string& name); + + // Destroys the given channel. + void DestroyChannel(TransportChannel* channel); + + // Note: This function is a hack and should not be used. + TransportChannelImpl* GetImplementation(TransportChannel* channel); + + // Invoked when we notice that there is no matching channel on our peer. + sigslot::signal2<Session*, const std::string&> SignalChannelGone; + + private: + typedef std::list<Transport*> TransportList; + typedef std::map<std::string, TransportChannelProxy*> ChannelMap; + + SessionManager *session_manager_; + std::string name_; + std::string remote_name_; + bool initiator_; + SessionID id_; + std::string session_type_; + SessionClient* client_; + const SessionDescription *description_; + const SessionDescription *remote_description_; + State state_; + Error error_; + std::string redirect_target_; + // Note that the following two members are mutually exclusive + TransportList potential_transports_; // order implies preference + Transport* transport_; // negotiated transport + ChannelMap channels_; + bool compatibility_mode_; // indicates talking to an old client + XmlElements candidates_; // holds candidates sent in case of compat-mode + + // Creates or destroys a session. (These are called only SessionManager.) + Session(SessionManager *session_manager, + const std::string& name, + const SessionID& id, + const std::string& session_type, + SessionClient* client); + ~Session(); + + // Updates the state, signaling if necessary. + void SetState(State state); + + // Updates the error state, signaling if necessary. + void SetError(Error error); + + // To improve connection time, this creates the channels on the most common + // transport type and initiates connection. + void ConnectDefaultTransportChannels(bool create); + + // If a new channel is created after we have created the default transport, + // then we should create this channel as well and let it connect. + void CreateDefaultTransportChannel(const std::string& name); + + // Creates a default set of transports if the client did not specify some. + void CreateTransports(); + + // Attempts to choose a transport that is in both our list and the other + // clients. This will examine the children of the given XML element to find + // the descriptions of the other client's transports. We will pick the first + // transport in the other client's list that we also support. + // (This is called only by SessionManager.) + bool ChooseTransport(const buzz::XmlElement* msg); + + // Called when a single transport has been negotiated. + void SetTransport(Transport* transport); + + // 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 OnTransportSendMessage(Transport* transport, const XmlElements& elems); + + // 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(); + + // Sends a message of the given type to the other client. The body will + // contain the given list of elements (which are consumed by the function). + void SendSessionMessage(const std::string& type, + const XmlElements& elems); + + // 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 buzz::XmlElement* stanza); + + 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<Session*, + 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 buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnAcceptMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnRejectMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnRedirectMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnInfoMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnTransportAcceptMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnTransportInfoMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnTerminateMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool OnCandidatesMessage(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + + // Helper functions for parsing various message types. CheckState verifies + // that we are in the appropriate state to receive this message. The latter + // three verify that an element has the required child or attribute. + bool CheckState(const buzz::XmlElement* stanza, State state); + bool FindRequiredElement(const buzz::XmlElement* stanza, + const buzz::XmlElement* parent, + const buzz::QName& name, + const buzz::XmlElement** elem); + bool FindRemoteSessionDescription(const buzz::XmlElement* stanza, + const buzz::XmlElement* session); + bool FindRequiredAttribute(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + const buzz::QName& name, + std::string* value); + + // Handles messages posted to us. + void OnMessage(talk_base::Message *pmsg); + + friend class SessionManager; // For access to constructor, destructor, + // and signaling related methods. +}; + +} // namespace cricket + +#endif // _SESSION_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/session_unittest.cc b/third_party/libjingle/files/talk/p2p/base/session_unittest.cc new file mode 100644 index 0000000..972ed9dc --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/session_unittest.cc @@ -0,0 +1,1039 @@ +#include <iostream> +#include <sstream> +#include <deque> +#include <map> + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/host.h" +#include "talk/base/natserver.h" +#include "talk/base/natsocketfactory.h" +#include "talk/base/helpers.h" +#include "talk/xmpp/constants.h" +#include "talk/p2p/base/constants.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/sessionclient.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/portallocator.h" +#include "talk/p2p/base/transportchannel.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/stunport.h" +#include "talk/p2p/base/relayport.h" +#include "talk/p2p/base/p2ptransport.h" +#include "talk/p2p/base/rawtransport.h" +#include "talk/p2p/base/stunserver.h" +#include "talk/p2p/base/relayserver.h" + +using namespace cricket; +using namespace buzz; + +const std::string kSessionType = "http://oink.splat/session"; + +const talk_base::SocketAddress kStunServerAddress("127.0.0.1", 7000); +const talk_base::SocketAddress kStunServerAddress2("127.0.0.1", 7001); + +const talk_base::SocketAddress kRelayServerIntAddress("127.0.0.1", 7002); +const talk_base::SocketAddress kRelayServerExtAddress("127.0.0.1", 7003); + +const int kNumPorts = 2; + +int gPort = 28653; +int GetNextPort() { + int p = gPort; + gPort += 5; + return p; +} + +int gID = 0; +std::string GetNextID() { + std::ostringstream ost; + ost << gID++; + return ost.str(); +} + +class TestPortAllocatorSession : public PortAllocatorSession { +public: + TestPortAllocatorSession(talk_base::Thread* worker_thread, talk_base::SocketFactory* factory, + const std::string& name, const std::string& session_type) + : PortAllocatorSession(0), worker_thread_(worker_thread), + factory_(factory), name_(name), ports_(kNumPorts), + address_("127.0.0.1", 0), network_("network", address_.ip()), + running_(false) { + } + + ~TestPortAllocatorSession() { + for (int i = 0; i < ports_.size(); i++) + delete ports_[i]; + } + + virtual void GetInitialPorts() { + // These are the flags set by the raw transport. + uint32 raw_flags = PORTALLOCATOR_DISABLE_UDP | PORTALLOCATOR_DISABLE_TCP; + + // If the client doesn't care, just give them two UDP ports. + if (flags() == 0) { + for (int i = 0; i < kNumPorts; i++) { + ports_[i] = new UDPPort(worker_thread_, factory_, &network_, + GetAddress()); + AddPort(ports_[i]); + } + + // If the client requested just stun and relay, we have to oblidge. + } else if (flags() == raw_flags) { + StunPort* sport = new StunPort(worker_thread_, factory_, &network_, + GetAddress(), kStunServerAddress); + sport->set_server_addr2(kStunServerAddress2); + ports_[0] = sport; + AddPort(sport); + + std::string username = CreateRandomString(16); + std::string password = CreateRandomString(16); + RelayPort* rport = new RelayPort(worker_thread_, factory_, &network_, + GetAddress(), username, password, ""); + rport->AddServerAddress( + ProtocolAddress(kRelayServerIntAddress, PROTO_UDP)); + ports_[1] = rport; + AddPort(rport); + } else { + ASSERT(false); + } + } + + virtual void StartGetAllPorts() { running_ = true; } + virtual void StopGetAllPorts() { running_ = false; } + virtual bool IsGettingAllPorts() { return running_; } + + talk_base::SocketAddress GetAddress() const { + talk_base::SocketAddress addr(address_); + addr.SetPort(GetNextPort()); + return addr; + } + + void AddPort(Port* port) { + port->set_name(name_); + port->set_preference(1.0); + port->set_generation(0); + port->SignalDestroyed.connect( + this, &TestPortAllocatorSession::OnPortDestroyed); + port->SignalAddressReady.connect( + this, &TestPortAllocatorSession::OnAddressReady); + port->PrepareAddress(); + SignalPortReady(this, port); + } + + void OnPortDestroyed(Port* port) { + for (int i = 0; i < ports_.size(); i++) { + if (ports_[i] == port) + ports_[i] = NULL; + } + } + + void OnAddressReady(Port* port) { + SignalCandidatesReady(this, port->candidates()); + } + +private: + talk_base::Thread* worker_thread_; + talk_base::SocketFactory* factory_; + std::string name_; + std::vector<Port*> ports_; + talk_base::SocketAddress address_; + talk_base::Network network_; + bool running_; +}; + +class TestPortAllocator : public PortAllocator { +public: + TestPortAllocator(talk_base::Thread* worker_thread, talk_base::SocketFactory* factory) + : worker_thread_(worker_thread), factory_(factory) { + if (factory_ == NULL) + factory_ = worker_thread_->socketserver(); + } + + virtual PortAllocatorSession *CreateSession(const std::string &name, const std::string &session_type) { + return new TestPortAllocatorSession(worker_thread_, factory_, name, session_type); + } + +private: + talk_base::Thread* worker_thread_; + talk_base::SocketFactory* factory_; +}; + +struct SessionManagerHandler : sigslot::has_slots<> { + SessionManagerHandler(SessionManager* m, const std::string& u) + : manager(m), username(u), create_count(0), destroy_count(0) { + manager->SignalSessionCreate.connect( + this, &SessionManagerHandler::OnSessionCreate); + manager->SignalSessionDestroy.connect( + this, &SessionManagerHandler::OnSessionDestroy); + manager->SignalOutgoingMessage.connect( + this, &SessionManagerHandler::OnOutgoingMessage); + manager->SignalRequestSignaling.connect( + this, &SessionManagerHandler::OnRequestSignaling); + } + + void OnSessionCreate(Session *session, bool initiate) { + create_count += 1; + last_id = session->id(); + } + + void OnSessionDestroy(Session *session) { + destroy_count += 1; + last_id = session->id(); + } + + void OnOutgoingMessage(const XmlElement* stanza) { + XmlElement* elem = new XmlElement(*stanza); + ASSERT(elem->Name() == QN_IQ); + ASSERT(elem->HasAttr(QN_TO)); + ASSERT(!elem->HasAttr(QN_FROM)); + ASSERT(elem->HasAttr(QN_TYPE)); + ASSERT((elem->Attr(QN_TYPE) == "set") || + (elem->Attr(QN_TYPE) == "result") || + (elem->Attr(QN_TYPE) == "error")); + + // Add in the appropriate "from". + elem->SetAttr(QN_FROM, username); + + // Add in the appropriate IQ ID. + if (elem->Attr(QN_TYPE) == "set") { + ASSERT(!elem->HasAttr(QN_ID)); + elem->SetAttr(QN_ID, GetNextID()); + } + + stanzas_.push_back(elem); + } + + void OnRequestSignaling() { + manager->OnSignalingReady(); + } + + + XmlElement* CheckNextStanza(const std::string& expected) { + // Get the next stanza, which should exist. + ASSERT(stanzas_.size() > 0); + XmlElement* stanza = stanzas_.front(); + stanzas_.pop_front(); + + // Make sure the stanza is correct. + std::string actual = stanza->Str(); + if (actual != expected) { + LOG(LERROR) << "Incorrect stanza: expected=\"" << expected + << "\" actual=\"" << actual << "\""; + ASSERT(actual == expected); + } + + return stanza; + } + + void CheckNoStanza() { + ASSERT(stanzas_.size() == 0); + } + + void PrintNextStanza() { + ASSERT(stanzas_.size() > 0); + printf("Stanza: %s\n", stanzas_.front()->Str().c_str()); + } + + SessionManager* manager; + std::string username; + SessionID last_id; + uint32 create_count; + uint32 destroy_count; + std::deque<XmlElement*> stanzas_; +}; + +struct SessionHandler : sigslot::has_slots<> { + SessionHandler(Session* s) : session(s) { + session->SignalState.connect(this, &SessionHandler::OnState); + session->SignalError.connect(this, &SessionHandler::OnError); + } + + void PrepareTransport() { + Transport* transport = session->GetTransport(kNsP2pTransport); + if (transport != NULL) + transport->set_allow_local_ips(true); + } + + void OnState(Session* session, Session::State state) { + ASSERT(session == this->session); + last_state = state; + } + + void OnError(Session* session, Session::Error error) { + ASSERT(session == this->session); + ASSERT(false); // errors are bad! + } + + Session* session; + Session::State last_state; +}; + +struct MySessionClient: public SessionClient, public sigslot::has_slots<> { + MySessionClient() : create_count(0), a(NULL), b(NULL) { } + + void AddManager(SessionManager* manager) { + manager->AddClient(kSessionType, this); + ASSERT(manager->GetClient(kSessionType) == this); + manager->SignalSessionCreate.connect( + this, &MySessionClient::OnSessionCreate); + } + + const SessionDescription* CreateSessionDescription( + const XmlElement* element) { + return new SessionDescription(); + } + + XmlElement* TranslateSessionDescription( + const SessionDescription* description) { + return new XmlElement(QName(kSessionType, "description")); + } + + void OnSessionCreate(Session *session, bool initiate) { + create_count += 1; + a = session->CreateChannel("a"); + b = session->CreateChannel("b"); + + if (transport_name.size() > 0) + session->SetPotentialTransports(&transport_name, 1); + } + + void OnSessionDestroy(Session *session) + { + } + + void SetTransports(bool p2p, bool raw) { + if (p2p && raw) + return; // this is the default + + if (p2p) { + transport_name = kNsP2pTransport; + } + } + + int create_count; + TransportChannel* a; + TransportChannel* b; + std::string transport_name; +}; + +struct ChannelHandler : sigslot::has_slots<> { + ChannelHandler(TransportChannel* p) + : channel(p), last_readable(false), last_writable(false), data_count(0), + last_size(0) { + p->SignalReadableState.connect(this, &ChannelHandler::OnReadableState); + p->SignalWritableState.connect(this, &ChannelHandler::OnWritableState); + p->SignalReadPacket.connect(this, &ChannelHandler::OnReadPacket); + } + + void OnReadableState(TransportChannel* p) { + ASSERT(p == channel); + last_readable = channel->readable(); + } + + void OnWritableState(TransportChannel* p) { + ASSERT(p == channel); + last_writable = channel->writable(); + } + + void OnReadPacket(TransportChannel* p, const char* buf, size_t size) { + ASSERT(p == channel); + ASSERT(size <= sizeof(last_data)); + data_count += 1; + last_size = size; + std::memcpy(last_data, buf, size); + } + + void Send(const char* data, size_t size) { + int result = channel->SendPacket(data, size); + ASSERT(result == static_cast<int>(size)); + } + + TransportChannel* channel; + bool last_readable, last_writable; + int data_count; + char last_data[4096]; + size_t last_size; +}; + +char* Reverse(const char* str) { + int len = strlen(str); + char* rev = new char[len+1]; + for (int i = 0; i < len; i++) + rev[i] = str[len-i-1]; + rev[len] = '\0'; + return rev; +} + +// Sets up values that should be the same for every test. +void InitTest() { + SetRandomSeed(7); + gPort = 28653; + gID = 0; +} + +// Tests having client2 accept the session. +void TestAccept(talk_base::Thread* signaling_thread, + Session* session1, Session* session2, + SessionHandler* handler1, SessionHandler* handler2, + SessionManager* manager1, SessionManager* manager2, + SessionManagerHandler* manhandler1, + SessionManagerHandler* manhandler2) { + // Make sure the IQ ID is 5. + ASSERT(gID <= 5); + while (gID < 5) GetNextID(); + + // Accept the session. + SessionDescription* desc2 = new SessionDescription(); + bool valid = session2->Accept(desc2); + ASSERT(valid); + + scoped_ptr<buzz::XmlElement> stanza; + stanza.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"5\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"accept\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + + // Simulate a tiny delay in sending. + signaling_thread->ProcessMessages(10); + + // Delivery the accept. + manager1->OnIncomingMessage(stanza.get()); + stanza.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"5\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // Both sessions should be in progress after a short wait. + signaling_thread->ProcessMessages(10); + ASSERT(handler1->last_state == Session::STATE_INPROGRESS); + ASSERT(handler2->last_state == Session::STATE_INPROGRESS); +} + +// Tests sending data between two clients, over two channels. +void TestSendRecv(ChannelHandler* chanhandler1a, ChannelHandler* chanhandler1b, + ChannelHandler* chanhandler2a, ChannelHandler* chanhandler2b, + talk_base::Thread* signaling_thread, bool first_dropped) { + const char* dat1a = "spamspamspamspamspamspamspambakedbeansspam"; + const char* dat1b = "Lobster Thermidor a Crevette with a mornay sauce..."; + const char* dat2a = Reverse(dat1a); + const char* dat2b = Reverse(dat1b); + + // Sending from 2 -> 1 will enable 1 to send to 2 below. That will then + // enable 2 to send back to 1. So the code below will just work. + if (first_dropped) { + chanhandler2a->Send(dat2a, strlen(dat2a)); + chanhandler2b->Send(dat2b, strlen(dat2b)); + } + + for (int i = 0; i < 20; i++) { + chanhandler1a->Send(dat1a, strlen(dat1a)); + chanhandler1b->Send(dat1b, strlen(dat1b)); + chanhandler2a->Send(dat2a, strlen(dat2a)); + chanhandler2b->Send(dat2b, strlen(dat2b)); + + signaling_thread->ProcessMessages(10); + + ASSERT(chanhandler1a->data_count == i + 1); + ASSERT(chanhandler1b->data_count == i + 1); + ASSERT(chanhandler2a->data_count == i + 1); + ASSERT(chanhandler2b->data_count == i + 1); + + ASSERT(chanhandler1a->last_size == strlen(dat2a)); + ASSERT(chanhandler1b->last_size == strlen(dat2b)); + ASSERT(chanhandler2a->last_size == strlen(dat1a)); + ASSERT(chanhandler2b->last_size == strlen(dat1b)); + + ASSERT(std::memcmp(chanhandler1a->last_data, dat2a, strlen(dat2a)) == 0); + ASSERT(std::memcmp(chanhandler1b->last_data, dat2b, strlen(dat2b)) == 0); + ASSERT(std::memcmp(chanhandler2a->last_data, dat1a, strlen(dat1a)) == 0); + ASSERT(std::memcmp(chanhandler2b->last_data, dat1b, strlen(dat1b)) == 0); + } +} + +// Tests a session between two clients. The inputs indicate whether we should +// replace each client's output with what we would see from an old client. +void TestP2PCompatibility(const std::string& test_name, bool old1, bool old2) { + InitTest(); + + talk_base::Thread* signaling_thread = talk_base::Thread::Current(); + scoped_ptr<talk_base::Thread> worker_thread(new talk_base::Thread()); + worker_thread->Start(); + + scoped_ptr<PortAllocator> allocator( + new TestPortAllocator(worker_thread.get(), NULL)); + scoped_ptr<MySessionClient> client(new MySessionClient()); + client->SetTransports(true, false); + + scoped_ptr<SessionManager> manager1( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler1( + new SessionManagerHandler(manager1.get(), "foo@baz.com")); + client->AddManager(manager1.get()); + + Session* session1 = manager1->CreateSession("foo@baz.com", kSessionType); + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler1->last_id == session1->id()); + scoped_ptr<SessionHandler> handler1(new SessionHandler(session1)); + + ASSERT(client->create_count == 1); + TransportChannel* chan1a = client->a; + ASSERT(chan1a->name() == "a"); + ASSERT(session1->GetChannel("a") == chan1a); + scoped_ptr<ChannelHandler> chanhandler1a(new ChannelHandler(chan1a)); + TransportChannel* chan1b = client->b; + ASSERT(chan1b->name() == "b"); + ASSERT(session1->GetChannel("b") == chan1b); + scoped_ptr<ChannelHandler> chanhandler1b(new ChannelHandler(chan1b)); + + SessionDescription* desc1 = new SessionDescription(); + ASSERT(session1->state() == Session::STATE_INIT); + bool valid = session1->Initiate("bar@baz.com", NULL, desc1); + ASSERT(valid); + handler1->PrepareTransport(); + + signaling_thread->ProcessMessages(100); + + ASSERT(handler1->last_state == Session::STATE_SENTINITIATE); + scoped_ptr<XmlElement> stanza1, stanza2; + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler1->CheckNoStanza(); + + // If the first client were old, the initiate would have no transports and + // the candidates would be sent in a candidates message. + if (old1) { + stanza1.reset(XmlElement::ForStr( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "</session>" + "</cli:iq>")); + stanza2.reset(XmlElement::ForStr( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>")); + } + + scoped_ptr<SessionManager> manager2( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler2( + new SessionManagerHandler(manager2.get(), "bar@baz.com")); + client->AddManager(manager2.get()); + + // Deliver the initiate. + manager2->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"0\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + // If client1 is old, we will not see a transport-accept. If client2 is old, + // then we should act as if it did not send one. + if (!old1) { + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"2\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\"" + " type=\"transport-accept\" id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + } else { + GetNextID(); // Advance the ID count to be the same in all cases. + stanza1.reset(NULL); + } + if (old2) { + stanza1.reset(NULL); + } + manhandler2->CheckNoStanza(); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler2->last_id == session1->id()); + + Session* session2 = manager2->GetSession(session1->id()); + ASSERT(session2); + ASSERT(session1->id() == session2->id()); + ASSERT(manhandler2->last_id == session2->id()); + ASSERT(session2->state() == Session::STATE_RECEIVEDINITIATE); + scoped_ptr<SessionHandler> handler2(new SessionHandler(session2)); + handler2->PrepareTransport(); + + ASSERT(session2->name() == session1->remote_name()); + ASSERT(session1->name() == session2->remote_name()); + + ASSERT(session2->transport() != NULL); + ASSERT(session2->transport()->name() == kNsP2pTransport); + + ASSERT(client->create_count == 2); + TransportChannel* chan2a = client->a; + scoped_ptr<ChannelHandler> chanhandler2a(new ChannelHandler(chan2a)); + TransportChannel* chan2b = client->b; + scoped_ptr<ChannelHandler> chanhandler2b(new ChannelHandler(chan2b)); + + // Deliver the candidates. + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"1\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + signaling_thread->ProcessMessages(10); + + // If client1 is old, we should see a candidates message instead of a + // transport-info. If client2 is old, we should act as if we did. + const char* kCandidates2 = + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>"; + if (old1) { + stanza2.reset(manhandler2->CheckNextStanza(kCandidates2)); + } else { + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + } + if (old2) { + stanza2.reset(XmlElement::ForStr(kCandidates2)); + } + manhandler2->CheckNoStanza(); + + // Deliver the transport-accept if one exists. + if (stanza1.get() != NULL) { + manager1->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"2\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session should now have a transport. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + } + + // Deliver the candidates. If client2 is old (or is acting old because + // client1 is), then client1 will correct its earlier mistake of sending + // transport-info by sending a candidates message. If client1 is supposed to + // be old, then it sent candidates earlier, so we drop this. + manager1->OnIncomingMessage(stanza2.get()); + if (old1 || old2) { + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"4\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"candidates\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</session>" + "</cli:iq>")); + } else { + GetNextID(); // Advance the ID count to be the same in all cases. + stanza2.reset(NULL); + } + if (old1) { + stanza2.reset(NULL); + } + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"3\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session must have a transport in either case now. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + + // If client1 just generated a candidates message, then we must deliver it. + if (stanza2.get() != NULL) { + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"4\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler2->CheckNoStanza(); + } + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + signaling_thread->ProcessMessages(500); + ASSERT(chan1a->writable() && chan1a->readable()); + ASSERT(chan1b->writable() && chan1b->readable()); + ASSERT(chan2a->writable() && chan2a->readable()); + ASSERT(chan2b->writable() && chan2b->readable()); + ASSERT(chanhandler1a->last_writable); + ASSERT(chanhandler1b->last_writable); + ASSERT(chanhandler2a->last_writable); + ASSERT(chanhandler2b->last_writable); + + // Accept the session. + TestAccept(signaling_thread, session1, session2, + handler1.get(), handler2.get(), + manager1.get(), manager2.get(), + manhandler1.get(), manhandler2.get()); + + // Send a bunch of data between them. + TestSendRecv(chanhandler1a.get(), chanhandler1b.get(), chanhandler2a.get(), + chanhandler2b.get(), signaling_thread, false); + + manager1->DestroySession(session1); + manager2->DestroySession(session2); + + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler1->destroy_count == 1); + ASSERT(manhandler2->destroy_count == 1); + + worker_thread->Stop(); + + std::cout << "P2P Compatibility: " << test_name << ": PASS" << std::endl; +} + +// Tests the P2P transport. The flags indicate whether they clients will +// advertise support for raw as well. +void TestP2P(const std::string& test_name, bool raw1, bool raw2) { + InitTest(); + + talk_base::Thread* signaling_thread = talk_base::Thread::Current(); + scoped_ptr<talk_base::Thread> worker_thread(new talk_base::Thread()); + worker_thread->Start(); + + scoped_ptr<PortAllocator> allocator( + new TestPortAllocator(worker_thread.get(), NULL)); + scoped_ptr<MySessionClient> client1(new MySessionClient()); + client1->SetTransports(true, raw1); + scoped_ptr<MySessionClient> client2(new MySessionClient()); + client2->SetTransports(true, raw2); + + scoped_ptr<SessionManager> manager1( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler1( + new SessionManagerHandler(manager1.get(), "foo@baz.com")); + client1->AddManager(manager1.get()); + + Session* session1 = manager1->CreateSession("foo@baz.com", kSessionType); + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler1->last_id == session1->id()); + scoped_ptr<SessionHandler> handler1(new SessionHandler(session1)); + + ASSERT(client1->create_count == 1); + TransportChannel* chan1a = client1->a; + ASSERT(chan1a->name() == "a"); + ASSERT(session1->GetChannel("a") == chan1a); + scoped_ptr<ChannelHandler> chanhandler1a(new ChannelHandler(chan1a)); + TransportChannel* chan1b = client1->b; + ASSERT(chan1b->name() == "b"); + ASSERT(session1->GetChannel("b") == chan1b); + scoped_ptr<ChannelHandler> chanhandler1b(new ChannelHandler(chan1b)); + + SessionDescription* desc1 = new SessionDescription(); + ASSERT(session1->state() == Session::STATE_INIT); + bool valid = session1->Initiate("bar@baz.com", NULL, desc1); + ASSERT(valid); + handler1->PrepareTransport(); + + signaling_thread->ProcessMessages(100); + + ASSERT(handler1->last_state == Session::STATE_SENTINITIATE); + scoped_ptr<XmlElement> stanza1, stanza2; + if (raw1) { + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "<raw:transport xmlns:raw=\"http://www.google.com/transport/raw\"/>" + "</session>" + "</cli:iq>")); + } else { + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"0\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"initiate\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<ses:description xmlns:ses=\"http://oink.splat/session\"/>" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + } + stanza2.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" type=\"set\" from=\"foo@baz.com\" id=\"1\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28653\"" + " preference=\"1\" username=\"h0ISP4S5SJKH/9EY\" protocol=\"udp\"" + " generation=\"0\" password=\"UhnAmO5C89dD2dZ+\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28658\"" + " preference=\"1\" username=\"yid4vfB3zXPvrRB9\" protocol=\"udp\"" + " generation=\"0\" password=\"SqLXTvcEyriIo+Mj\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28663\"" + " preference=\"1\" username=\"NvT78D7WxPWM1KL8\" protocol=\"udp\"" + " generation=\"0\" password=\"+mV/QhOapXu4caPX\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28668\"" + " preference=\"1\" username=\"8EzB7MH+TYpIlSp/\" protocol=\"udp\"" + " generation=\"0\" password=\"h+MelLXupoK5aYqC\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler1->CheckNoStanza(); + + scoped_ptr<SessionManager> manager2( + new SessionManager(allocator.get(), worker_thread.get())); + scoped_ptr<SessionManagerHandler> manhandler2( + new SessionManagerHandler(manager2.get(), "bar@baz.com")); + client2->AddManager(manager2.get()); + + // Deliver the initiate. + manager2->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"0\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + stanza1.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"2\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\"" + " type=\"transport-accept\" id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\"/>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler2->last_id == session1->id()); + + Session* session2 = manager2->GetSession(session1->id()); + ASSERT(session2); + ASSERT(session1->id() == session2->id()); + ASSERT(manhandler2->last_id == session2->id()); + ASSERT(session2->state() == Session::STATE_RECEIVEDINITIATE); + scoped_ptr<SessionHandler> handler2(new SessionHandler(session2)); + handler2->PrepareTransport(); + + ASSERT(session2->name() == session1->remote_name()); + ASSERT(session1->name() == session2->remote_name()); + + ASSERT(session2->transport() != NULL); + ASSERT(session2->transport()->name() == kNsP2pTransport); + + ASSERT(client2->create_count == 1); + TransportChannel* chan2a = client2->a; + scoped_ptr<ChannelHandler> chanhandler2a(new ChannelHandler(chan2a)); + TransportChannel* chan2b = client2->b; + scoped_ptr<ChannelHandler> chanhandler2b(new ChannelHandler(chan2b)); + + // Deliver the candidates. + manager2->OnIncomingMessage(stanza2.get()); + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" id=\"1\" type=\"result\" from=\"bar@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + + signaling_thread->ProcessMessages(10); + + stanza2.reset(manhandler2->CheckNextStanza( + "<cli:iq to=\"foo@baz.com\" type=\"set\" from=\"bar@baz.com\" id=\"3\"" + " xmlns:cli=\"jabber:client\">" + "<session xmlns=\"http://www.google.com/session\" type=\"transport-info\"" + " id=\"2154761789\" initiator=\"foo@baz.com\">" + "<p:transport xmlns:p=\"http://www.google.com/transport/p2p\">" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28673\"" + " preference=\"1\" username=\"FJDz3iuXjbQJDRjs\" protocol=\"udp\"" + " generation=\"0\" password=\"Ca5daV9m6G91qhlM\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"a\" address=\"127.0.0.1\" port=\"28678\"" + " preference=\"1\" username=\"xlN53r3Jn/R5XuCt\" protocol=\"udp\"" + " generation=\"0\" password=\"rgik2pKsjaPSUdJd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28683\"" + " preference=\"1\" username=\"IBZ8CSq8ot2+pSMp\" protocol=\"udp\"" + " generation=\"0\" password=\"i7RcDsGntMI6fzdd\" type=\"local\"" + " network=\"network\"/>" + "<candidate name=\"b\" address=\"127.0.0.1\" port=\"28688\"" + " preference=\"1\" username=\"SEtih9PYtMHCAlMI\" protocol=\"udp\"" + " generation=\"0\" password=\"wROrHJ3+gDxUUMp1\" type=\"local\"" + " network=\"network\"/>" + "</p:transport>" + "</session>" + "</cli:iq>")); + manhandler2->CheckNoStanza(); + + // Deliver the transport-accept. + manager1->OnIncomingMessage(stanza1.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"2\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The first session should now have a transport. + ASSERT(session1->transport() != NULL); + ASSERT(session1->transport()->name() == kNsP2pTransport); + + // Deliver the candidates. + manager1->OnIncomingMessage(stanza2.get()); + stanza1.reset(manhandler1->CheckNextStanza( + "<cli:iq to=\"bar@baz.com\" id=\"3\" type=\"result\" from=\"foo@baz.com\"" + " xmlns:cli=\"jabber:client\"/>")); + manhandler1->CheckNoStanza(); + + // The channels should be able to become writable at this point. This + // requires pinging, so it may take a little while. + signaling_thread->ProcessMessages(500); + ASSERT(chan1a->writable() && chan1a->readable()); + ASSERT(chan1b->writable() && chan1b->readable()); + ASSERT(chan2a->writable() && chan2a->readable()); + ASSERT(chan2b->writable() && chan2b->readable()); + ASSERT(chanhandler1a->last_writable); + ASSERT(chanhandler1b->last_writable); + ASSERT(chanhandler2a->last_writable); + ASSERT(chanhandler2b->last_writable); + + // Accept the session. + TestAccept(signaling_thread, session1, session2, + handler1.get(), handler2.get(), + manager1.get(), manager2.get(), + manhandler1.get(), manhandler2.get()); + + // Send a bunch of data between them. + TestSendRecv(chanhandler1a.get(), chanhandler1b.get(), chanhandler2a.get(), + chanhandler2b.get(), signaling_thread, false); + + manager1->DestroySession(session1); + manager2->DestroySession(session2); + + ASSERT(manhandler1->create_count == 1); + ASSERT(manhandler2->create_count == 1); + ASSERT(manhandler1->destroy_count == 1); + ASSERT(manhandler2->destroy_count == 1); + + worker_thread->Stop(); + + std::cout << "P2P: " << test_name << ": PASS" << std::endl; +} +// +int main(int argc, char* argv[]) { + talk_base::LogMessage::LogToDebug(talk_base::LS_WARNING); + + TestP2P("{p2p} => {p2p}", false, false); + TestP2P("{p2p} => {p2p,raw}", false, true); + TestP2P("{p2p,raw} => {p2p}", true, false); + TestP2P("{p2p,raw} => {p2p,raw}", true, true); + TestP2PCompatibility("New => New", false, false); + TestP2PCompatibility("Old => New", true, false); + TestP2PCompatibility("New => Old", false, true); + TestP2PCompatibility("Old => Old", true, true); + + return 0; +} diff --git a/third_party/libjingle/files/talk/p2p/base/sessionclient.h b/third_party/libjingle/files/talk/p2p/base/sessionclient.h new file mode 100644 index 0000000..a92df1d --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/sessionclient.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 _CRICKET_P2P_BASE_SESSIONCLIENT_H_ +#define _CRICKET_P2P_BASE_SESSIONCLIENT_H_ + +namespace buzz { +class XmlElement; +} + +namespace cricket { + +class Session; +class SessionDescription; + +// 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: + // 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; + + // Provides functions to convert between the XML description of the session + // and the data structures useful to the client. The resulting objects are + // held by the Session for easy access. + virtual const SessionDescription* CreateSessionDescription( + const buzz::XmlElement* element) = 0; + virtual buzz::XmlElement* TranslateSessionDescription( + const SessionDescription* description) = 0; + +protected: + // The SessionClient interface explicitly does not include destructor + virtual ~SessionClient() { } +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_SESSIONCLIENT_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/sessiondescription.h b/third_party/libjingle/files/talk/p2p/base/sessiondescription.h new file mode 100644 index 0000000..28b7084 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/sessiondescription.h @@ -0,0 +1,42 @@ +/* + * 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 _SESSIONDESCRIPTION_H_ +#define _SESSIONDESCRIPTION_H_ + +namespace cricket { + +// The client overrides this with whatever + +class SessionDescription { +public: + virtual ~SessionDescription() {} +}; + +} // namespace cricket + +#endif // _SESSIONDESCRIPTION_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/sessionid.h b/third_party/libjingle/files/talk/p2p/base/sessionid.h new file mode 100644 index 0000000..a12535c --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/sessionid.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 _SESSIONID_H_ +#define _SESSIONID_H_ + +#include "talk/base/basictypes.h" +#include <string> +#include <sstream> + +namespace cricket { + +// Each session is identified by a pair (from,id), where id is only +// assumed to be unique to the machine identified by from. +class SessionID { +public: + SessionID() : id_str_("0") { + } + SessionID(const std::string& initiator, uint32 id) + : initiator_(initiator) { + set_id(id); + } + SessionID(const SessionID& sid) + : id_str_(sid.id_str_), initiator_(sid.initiator_) { + } + + void set_id(uint32 id) { + std::stringstream st; + st << id; + st >> id_str_; + } + const std::string id_str() const { + return id_str_; + } + void set_id_str(const std::string &id_str) { + id_str_ = id_str; + } + + const std::string &initiator() const { + return initiator_; + } + void set_initiator(const std::string &initiator) { + initiator_ = initiator; + } + + bool operator <(const SessionID& sid) const { + int r = initiator_.compare(sid.initiator_); + if (r == 0) + r = id_str_.compare(sid.id_str_); + return r < 0; + } + + bool operator ==(const SessionID& sid) const { + return (id_str_ == sid.id_str_) && (initiator_ == sid.initiator_); + } + + SessionID& operator =(const SessionID& sid) { + id_str_ = sid.id_str_; + initiator_ = sid.initiator_; + return *this; + } + +private: + std::string id_str_; + std::string initiator_; +}; + +} // namespace cricket + +#endif // _SESSIONID_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/sessionmanager.cc b/third_party/libjingle/files/talk/p2p/base/sessionmanager.cc new file mode 100644 index 0000000..ae71a75 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/sessionmanager.cc @@ -0,0 +1,331 @@ +/* + * 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/p2p/base/constants.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& session_type, + SessionClient* client) { + ASSERT(client_map_.find(session_type) == client_map_.end()); + client_map_[session_type] = client; +} + +void SessionManager::RemoveClient(const std::string& session_type) { + ClientMap::iterator iter = client_map_.find(session_type); + ASSERT(iter != client_map_.end()); + client_map_.erase(iter); +} + +SessionClient* SessionManager::GetClient(const std::string& session_type) { + ClientMap::iterator iter = client_map_.find(session_type); + return (iter != client_map_.end()) ? iter->second : NULL; +} + +Session *SessionManager::CreateSession(const std::string& name, + const std::string& session_type) { + return CreateSession(name, SessionID(name, CreateRandomId()), session_type, + false); +} + +Session *SessionManager::CreateSession( + const std::string &name, const SessionID& id, + const std::string& session_type, bool received_initiate) { + SessionClient* client = GetClient(session_type); + ASSERT(client != NULL); + + Session *session = new Session(this, name, id, session_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 SessionID& id) { + SessionMap::iterator it = session_map_.find(id); + 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) { + if (stanza->Name() != buzz::QN_IQ) + return false; + if (!stanza->HasAttr(buzz::QN_TYPE)) + return false; + if (stanza->Attr(buzz::QN_TYPE) != buzz::STR_SET) + return false; + + const buzz::XmlElement* session = stanza->FirstNamed(QN_SESSION); + if (!session) + return false; + if (!session->HasAttr(buzz::QN_TYPE)) + return false; + if (!session->HasAttr(buzz::QN_ID) || !session->HasAttr(QN_INITIATOR)) + return false; + + return true; +} + +Session* SessionManager::FindSessionForStanza(const buzz::XmlElement* stanza, + bool incoming) { + const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION); + ASSERT(session_xml != NULL); + + SessionID id; + id.set_id_str(session_xml->Attr(buzz::QN_ID)); + id.set_initiator(session_xml->Attr(QN_INITIATOR)); + + // Pass this message to the session in question. + SessionMap::iterator iter = session_map_.find(id); + if (iter == session_map_.end()) + return NULL; + + Session* session = iter->second; + + // match on "from"? or "to"? + buzz::QName attr = buzz::QN_TO; + if (incoming) { + attr = buzz::QN_FROM; + } + buzz::Jid remote(session->remote_name()); + buzz::Jid match(stanza->Attr(attr)); + if (remote == match) { + return session; + } + return NULL; +} + +void SessionManager::OnIncomingMessage(const buzz::XmlElement* stanza) { + ASSERT(stanza->Attr(buzz::QN_TYPE) == buzz::STR_SET); + + Session* session = FindSessionForStanza(stanza, true); + if (session) { + session->OnIncomingMessage(stanza); + return; + } + + const buzz::XmlElement* session_xml = stanza->FirstNamed(QN_SESSION); + ASSERT(session_xml != NULL); + if (session_xml->Attr(buzz::QN_TYPE) == "initiate") { + std::string session_type = FindClient(session_xml); + if (session_type.size() == 0) { + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + "unknown session description type", NULL); + } else { + SessionID id; + id.set_id_str(session_xml->Attr(buzz::QN_ID)); + id.set_initiator(session_xml->Attr(QN_INITIATOR)); + + session = CreateSession(stanza->Attr(buzz::QN_TO), + id, + session_type, true); + session->OnIncomingMessage(stanza); + + // If we haven't rejected, and we haven't selected a transport yet, + // let's do it now. + if ((session->state() != Session::STATE_SENTREJECT) && + (session->transport() == NULL)) { + session->ChooseTransport(stanza); + } + } + return; + } + + SendErrorMessage(stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + "unknown session", NULL); +} + +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) { + Session* session = FindSessionForStanza(orig_stanza, false); + if (session) { + 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); + } +} + +std::string SessionManager::FindClient(const buzz::XmlElement* session) { + for (const buzz::XmlElement* elem = session->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + if (elem->Name().LocalPart() == "description") { + ClientMap::iterator iter = client_map_.find(elem->Name().Namespace()); + if (iter != client_map_.end()) + return iter->first; + } + } + return ""; +} + +void SessionManager::SendErrorMessage(const buzz::XmlElement* stanza, + const buzz::QName& name, + const std::string& type, + const std::string& text, + const buzz::XmlElement* extra_info) { + scoped_ptr<buzz::XmlElement> msg( + CreateErrorMessage(stanza, name, type, text, extra_info)); + SignalOutgoingMessage(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"); + + for (const buzz::XmlElement* elem = stanza->FirstElement(); + elem != NULL; + elem = elem->NextElement()) { + iq->AddElement(new buzz::XmlElement(*elem)); + } + + 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(stanza); +} + +void SessionManager::OnErrorMessage(Session* 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/third_party/libjingle/files/talk/p2p/base/sessionmanager.h b/third_party/libjingle/files/talk/p2p/base/sessionmanager.h new file mode 100644 index 0000000..423af65 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/sessionmanager.h @@ -0,0 +1,181 @@ +/* + * 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 _SESSIONMANAGER_H_ +#define _SESSIONMANAGER_H_ + +#include "talk/base/thread.h" +#include "talk/p2p/base/portallocator.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessionid.h" +#include "talk/base/sigslot.h" + +#include <string> +#include <utility> +#include <map> + +namespace buzz { +class QName; +class XmlElement; +} + +namespace cricket { + +class Session; +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& session_type, SessionClient* client); + void RemoveClient(const std::string& session_type); + SessionClient* GetClient(const std::string& session_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& name, + const std::string& session_type); + + // Destroys the given session. + void DestroySession(Session *session); + + // Returns the session with the given ID or NULL if none exists. + Session *GetSession(const SessionID& id); + + // 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 stanza, this find the Session associated with that stanza + Session* FindSessionForStanza(const buzz::XmlElement* stanza, bool incoming); + + // 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. + sigslot::signal1<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<SessionID, 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& name, + const SessionID& id, + const std::string& session_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* SessionManager::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(Session* 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 // _SESSIONMANAGER_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/stun.cc b/third_party/libjingle/files/talk/p2p/base/stun.cc new file mode 100644 index 0000000..c4a89e8 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stun.cc @@ -0,0 +1,578 @@ +/* + * 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/logging.h" +#include "talk/p2p/base/stun.h" +#include <iostream> +#include <cassert> + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::memcpy; +} +#endif + +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 (!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" + << " (" << (int)rest << " != " << (int)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, (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_); + assert((0 <= index) && (index < length())); + return static_cast<uint8>(bytes_[index]); +} + +void StunByteStringAttribute::SetByte(int index, uint8 value) { + assert(bytes_); + 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 + (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((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/third_party/libjingle/files/talk/p2p/base/stun.h b/third_party/libjingle/files/talk/p2p/base/stun.h new file mode 100644 index 0000000..4c0459b --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stun.h @@ -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. + */ + +#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 "talk/base/basictypes.h" +#include "talk/base/bytebuffer.h" +#include <string> +#include <vector> + +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/third_party/libjingle/files/talk/p2p/base/stunport.cc b/third_party/libjingle/files/talk/p2p/base/stunport.cc new file mode 100644 index 0000000..8fa3fd8 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunport.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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include <iostream> +#include <cassert> +#include "talk/base/common.h" +#include "talk/base/logging.h" +#include "talk/base/helpers.h" +#include "talk/p2p/base/stunport.h" + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +#include <errno.h> +#endif // POSIX + +namespace cricket { + +const int KEEPALIVE_DELAY = 10 * 1000; // 10 seconds - sort timeouts +const int RETRY_DELAY = 50; // 50ms, from ICE spec +const uint32 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::GetMillisecondCount(); + } + + 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(LERROR) << "Binding response missing mapped address."; + } else if (addr_attr->family() != 1) { + LOG(LERROR) << "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(LERROR) << "Bad allocate response error code"; + } else { + LOG(LERROR) << "Binding error response:" + << " class=" << attr->error_class() + << " number=" << attr->number() + << " reason='" << attr->reason() << "'"; + } + + port_->SignalAddressError(port_); + + if (keep_alive_ + && (talk_base::GetMillisecondCount() - start_time_ <= RETRY_TIMEOUT)) { + port_->requests_.SendDelayed( + new StunPortBindingRequest(port_, true, server_addr_), + KEEPALIVE_DELAY); + } + } + + virtual void OnTimeout() { + LOG(LERROR) << "Binding request timed out from " + << port_->GetLocalAddress().ToString() + << " (" << port_->network()->name() << ")"; + + port_->SignalAddressError(port_); + + if (keep_alive_ + && (talk_base::GetMillisecondCount() - 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::SocketFactory* factory, + talk_base::Network* network, + const talk_base::SocketAddress& local_addr, + const talk_base::SocketAddress& server_addr) + : UDPPort(thread, STUN_PORT_TYPE, factory, network), + server_addr_(server_addr), requests_(thread), error_(0) { + + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &StunPort::OnReadPacket); + if (socket_->Bind(local_addr) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; + + requests_.SignalSendPacket.connect(this, &StunPort::OnSendPacket); +} + +StunPort::~StunPort() { + delete socket_; +} + +void StunPort::PrepareAddress() { + // We will keep pinging the stun server to make sure our NAT pin-hole stays + // open during the call. + requests_.Send(new StunPortBindingRequest(this, true, server_addr_)); +} + +void StunPort::PrepareSecondaryAddress() { + ASSERT(!server_addr2_.IsAny()); + requests_.Send(new StunPortBindingRequest(this, false, server_addr2_)); +} + +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(); + 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( + const char* data, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + assert(socket == socket_); + + // Look for a response to a binding request. + if (requests_.CheckResponse(data, size)) + return; + + // Process this data packet in the normal manner. + UDPPort::OnReadPacket(data, size, remote_addr); +} + +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/third_party/libjingle/files/talk/p2p/base/stunport.h b/third_party/libjingle/files/talk/p2p/base/stunport.h new file mode 100644 index 0000000..4b62181 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunport.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 __STUNPORT_H__ +#define __STUNPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/udpport.h" +#include "talk/p2p/base/stunrequest.h" + +namespace cricket { + +extern const std::string STUN_PORT_TYPE; + +// Communicates using the address on the outside of a NAT. +class StunPort : public UDPPort { +public: + StunPort(talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, + const talk_base::SocketAddress& local_addr, + const talk_base::SocketAddress& server_addr); + 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(); + + talk_base::SocketAddress GetLocalAddress() const { + if (socket_) + return socket_->GetLocalAddress(); + return talk_base::SocketAddress(); + } + + virtual int SetOption(talk_base::Socket::Option opt, int value); + virtual int GetError(); + +protected: + virtual int SendTo(const void* data, size_t size, + const talk_base::SocketAddress& addr, bool payload); + + void OnReadPacket( + const char* data, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + +private: + talk_base::AsyncPacketSocket* socket_; + talk_base::SocketAddress server_addr_; + talk_base::SocketAddress server_addr2_; + StunRequestManager requests_; + int error_; + + friend class StunPortBindingRequest; + + // Sends STUN requests to the server. + void OnSendPacket(const void* data, size_t size, StunRequest* req); +}; + +} // namespace cricket + +#endif // __STUNPORT_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/stunrequest.cc b/third_party/libjingle/files/talk/p2p/base/stunrequest.cc new file mode 100644 index 0000000..66b8ef6 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunrequest.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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/base/helpers.h" +#include "talk/p2p/base/stunrequest.h" +#include <iostream> +#include <cassert> + +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()); + 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() + : manager_(0), id_(CreateRandomString(16)), msg_(0), count_(0), + timeout_(false), tstamp_(0) { +} + +StunRequest::StunRequest(StunMessage* request) + : manager_(0), id_(request->transaction_id()), msg_(request), + count_(0), timeout_(false) { +} + +StunRequest::~StunRequest() { + assert(manager_ != NULL); + if (manager_) { + manager_->Remove(this); + manager_->thread_->Clear(this); + } + delete msg_; +} + +const StunMessageType StunRequest::type() { + assert(msg_); + return msg_->type(); +} + +void StunRequest::set_manager(StunRequestManager* manager) { + assert(!manager_); + manager_ = manager; +} + +void StunRequest::OnMessage(talk_base::Message* pmsg) { + assert(manager_); + assert(pmsg->message_id == MSG_STUN_SEND); + + if (!msg_) { + msg_ = new StunMessage(); + msg_->SetTransactionID(id_); + Prepare(msg_); + assert(msg_->transaction_id() == id_); + } + + if (timeout_) { + OnTimeout(); + delete this; + return; + } + + tstamp_ = talk_base::GetMillisecondCount(); + + 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::GetMillisecondCount() - 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/third_party/libjingle/files/talk/p2p/base/stunrequest.h b/third_party/libjingle/files/talk/p2p/base/stunrequest.h new file mode 100644 index 0000000..b36861c --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunrequest.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. + */ + +#ifndef __STUNREQUESTMANAGER_H__ +#define __STUNREQUESTMANAGER_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(); + + // 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. + const 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 the actual request to be sent. Note that the 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 // __STUNREQUESTMANAGER_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/stunserver.cc b/third_party/libjingle/files/talk/p2p/base/stunserver.cc new file mode 100644 index 0000000..5fc61ac --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunserver.cc @@ -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. + */ + +#include "talk/base/bytebuffer.h" +#include "talk/p2p/base/stunserver.h" +#include <iostream> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +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( + const char* buf, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + + // 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. + talk_base::SocketAddress local_addr = socket_->GetLocalAddress(); + 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) + std::cerr << "sendto: " << std::strerror(errno) << std::endl; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/stunserver.h b/third_party/libjingle/files/talk/p2p/base/stunserver.h new file mode 100644 index 0000000..0e57a18 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunserver.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 __STUNSERVER_H__ +#define __STUNSERVER_H__ + +#include "talk/base/asyncudpsocket.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. + StunServer(talk_base::AsyncUDPSocket* socket); + + // Removes the STUN server from the socket, but does not delete the socket. + ~StunServer(); + +protected: + + // Slot for AsyncSocket.PacketRead: + void OnPacket( + const char* buf, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + + // 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::AsyncUDPSocket* socket_; +}; + +} // namespace cricket + +#endif // __STUNSERVER_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/stunserver_main.cc b/third_party/libjingle/files/talk/p2p/base/stunserver_main.cc new file mode 100644 index 0000000..8aa200e --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunserver_main.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/host.h" +#include "talk/base/thread.h" +#include "talk/p2p/base/stunserver.h" +#include <iostream> + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +using namespace cricket; + +int main(int argc, char* argv[]) { + if (argc != 1) { + std::cerr << "usage: stunserver" << std::endl; + return 1; + } + + talk_base::SocketAddress server_addr(talk_base::LocalHost().networks()[1]->ip(), 7000); + + talk_base::Thread *pthMain = talk_base::Thread::Current(); + + talk_base::AsyncUDPSocket* server_socket = talk_base::CreateAsyncUDPSocket(pthMain->socketserver()); + if (server_socket->Bind(server_addr) < 0) { + std::cerr << "bind: " << std::strerror(errno) << std::endl; + return 1; + } + + StunServer* server = new StunServer(server_socket); + + std::cout << "Listening at " << server_addr.ToString() << std::endl; + + pthMain->Run(); + + delete server; + delete server_socket; + return 0; +} diff --git a/third_party/libjingle/files/talk/p2p/base/stunserver_unittest.cc b/third_party/libjingle/files/talk/p2p/base/stunserver_unittest.cc new file mode 100644 index 0000000..b56da4e --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/stunserver_unittest.cc @@ -0,0 +1,107 @@ +#include "talk/base/testclient.h" +#include "talk/base/thread.h" +#include "talk/base/physicalsocketserver.h" +#include "talk/base/host.h" +#include "talk/p2p/base/stunserver.h" +#include <cstring> +#include <iostream> +#include <cassert> + +using namespace cricket; + +StunMessage* GetResponse(talk_base::TestClient* client) { + talk_base::TestClient::Packet* packet = client->NextPacket(); + assert(packet); + talk_base::ByteBuffer buf(packet->buf, packet->size); + StunMessage* msg = new StunMessage(); + assert(msg->Read(&buf)); + delete packet; + return msg; +} + +int main(int argc, char* argv[]) { + assert(talk_base::LocalHost().networks().size() >= 2); + talk_base::SocketAddress server_addr(talk_base::LocalHost().networks()[1]->ip(), 7000); + talk_base::SocketAddress client_addr(talk_base::LocalHost().networks()[1]->ip(), 6000); + + talk_base::Thread th; + + talk_base::AsyncUDPSocket* server_socket = 0; + StunServer* server = 0; + if (argc >= 2) { + server_addr.SetIP(argv[1]); + client_addr.SetIP(0); + if (argc == 3) + server_addr.SetPort(atoi(argv[2])); + std::cout << "Using server at " << server_addr.ToString() << std::endl; + } else { + server_socket = talk_base::CreateAsyncUDPSocket(th.socketserver()); + assert(server_socket->Bind(server_addr) >= 0); + server = new StunServer(server_socket); + } + + talk_base::AsyncUDPSocket* client_socket = talk_base::CreateAsyncUDPSocket(th.socketserver()); + assert(client_socket->Bind(client_addr) >= 0); + talk_base::TestClient* client = new talk_base::TestClient(client_socket, &th); + + th.Start(); + + const char* bad = "this is a completely nonsensical message whose only " + "purpose is to make the parser go 'ack'. it doesn't " + "look anything like a normal stun message"; + + client->SendTo(bad, std::strlen(bad), server_addr); + StunMessage* msg = GetResponse(client); + assert(msg->type() == STUN_BINDING_ERROR_RESPONSE); + + const StunErrorCodeAttribute* err = msg->GetErrorCode(); + assert(err); + assert(err->error_class() == 4); + assert(err->number() == 0); + assert(err->reason() == std::string("Bad Request")); + + delete msg; + + std::string transaction_id = "0123456789abcdef"; + + StunMessage req; + req.SetType(STUN_BINDING_REQUEST); + req.SetTransactionID(transaction_id); + + talk_base::ByteBuffer buf; + req.Write(&buf); + + client->SendTo(buf.Data(), buf.Length(), server_addr); + StunMessage* msg2 = GetResponse(client); + assert(msg2->type() == STUN_BINDING_RESPONSE); + assert(msg2->transaction_id() == transaction_id); + + const StunAddressAttribute* mapped_addr = + msg2->GetAddress(STUN_ATTR_MAPPED_ADDRESS); + assert(mapped_addr); + assert(mapped_addr->family() == 1); + assert(mapped_addr->port() == client_addr.port()); + if (mapped_addr->ip() != client_addr.ip()) { + printf("Warning: mapped IP (%s) != local IP (%s)\n", + talk_base::SocketAddress::IPToString(mapped_addr->ip()).c_str(), + client_addr.IPAsString().c_str()); + } + + const StunAddressAttribute* source_addr = + msg2->GetAddress(STUN_ATTR_SOURCE_ADDRESS); + assert(source_addr); + assert(source_addr->family() == 1); + assert(source_addr->port() == server_addr.port()); + assert(source_addr->ip() == server_addr.ip()); + + delete msg2; + + th.Stop(); + + delete server; + delete server_socket; + delete client; + + std::cout << "PASS" << std::endl; + return 0; +} diff --git a/third_party/libjingle/files/talk/p2p/base/tcpport.cc b/third_party/libjingle/files/talk/p2p/base/tcpport.cc new file mode 100644 index 0000000..57e2a4f --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/tcpport.cc @@ -0,0 +1,271 @@ +/* + * 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 +extern "C" { +#include <errno.h> +} +#endif // POSIX + +#include <cassert> +#include <iostream> + +#include "talk/base/common.h" +#include "talk/base/logging.h" +#ifdef WIN32 +#include "talk/base/winfirewall.h" +#endif // WIN32 +#include "talk/p2p/base/tcpport.h" + +namespace cricket { + +#ifdef WIN32 +static talk_base::WinFirewall win_firewall; +#endif // WIN32 + +TCPPort::TCPPort(talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, + const talk_base::SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), address_(address), + incoming_only_(address_.port() != 0), error_(0) { + socket_ = thread->socketserver()->CreateAsyncSocket(SOCK_STREAM); + socket_->SignalReadEvent.connect(this, &TCPPort::OnAcceptEvent); + if (socket_->Bind(address_) < 0) { + LOG_F(LS_ERROR) << "Bind error: " << socket_->GetError(); + } +} + +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 0; + + // We can't accept TCP connections incoming on other ports + if (origin == ORIGIN_OTHER_PORT) + return 0; + + // Check if we are allowed to make outgoing TCP connections + if (incoming_only_ && (origin == ORIGIN_MESSAGE)) + return 0; + + // We don't know how to act as an ssl server yet + if ((address.protocol() == "ssltcp") && (origin == ORIGIN_THIS_PORT)) + return 0; + + TCPConnection* conn = 0; + if (talk_base::AsyncTCPSocket * 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() { + assert(socket_); + + bool allow_listen = true; +#ifdef WIN32 + if (win_firewall.Initialize()) { + char module_path[MAX_PATH + 1] = { 0 }; + ::GetModuleFileNameA(NULL, module_path, MAX_PATH); + if (win_firewall.Enabled() && !win_firewall.Authorized(module_path)) { + allow_listen = false; + } + } +#endif // WIN32 + if (!allow_listen) { + LOG_F(LS_VERBOSE) << "Not listening due to firewall restrictions"; + } else if (socket_->Listen(5) < 0) { + LOG_F(LS_ERROR) << "Listen error: " << socket_->GetError(); + } + // Note: We still add the address, since otherwise the remote side won't + // recognize our incoming TCP connections. + AddAddress(socket_->GetLocalAddress(), "tcp", true); +} + +int TCPPort::SendTo(const void* data, size_t size, + const talk_base::SocketAddress& addr, bool payload) { + talk_base::AsyncTCPSocket * socket = 0; + + if (TCPConnection * conn = static_cast<TCPConnection*>(GetConnection(addr))) { + socket = conn->socket(); + } else { + socket = GetIncoming(addr); + } + if (!socket) { + LOG_F(LS_ERROR) << "Unknown destination: " << addr.ToString(); + return -1; // TODO: Set error_ + } + + //LOG_F(INFO) << "(" << size << ", " << addr.ToString() << ")"; + + int sent = socket->Send(data, size); + if (sent < 0) { + error_ = socket->GetError(); + LOG_F(LS_ERROR) << "(" << size << ", " << addr.ToString() + << ") Send error: " << error_; + } + return sent; +} + +int TCPPort::SetOption(talk_base::Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int TCPPort::GetError() { + assert(socket_); + return error_; +} + +void TCPPort::OnAcceptEvent(talk_base::AsyncSocket* socket) { + assert(socket == socket_); + + Incoming incoming; + talk_base::AsyncSocket * newsocket + = static_cast<talk_base::AsyncSocket *>(socket->Accept(&incoming.addr)); + if (!newsocket) { + // TODO: Do something better like forwarding the error to the user. + LOG_F(LS_ERROR) << "Accept error: " << socket_->GetError(); + return; + } + incoming.socket = new talk_base::AsyncTCPSocket(newsocket); + incoming.socket->SignalReadPacket.connect(this, &TCPPort::OnReadPacket); + + LOG_F(LS_VERBOSE) << "(" << incoming.addr.ToString() << ")"; + incoming_.push_back(incoming); + + // Prime a read event in case data is waiting + newsocket->SignalReadEvent(newsocket); +} + +talk_base::AsyncTCPSocket * TCPPort::GetIncoming( + const talk_base::SocketAddress& addr, bool remove) { + talk_base::AsyncTCPSocket * socket = 0; + 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(const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + Port::OnReadPacket(data, size, remote_addr); +} + +TCPConnection::TCPConnection(TCPPort* port, const Candidate& candidate, + talk_base::AsyncTCPSocket* socket) + : Connection(port, 0, candidate), socket_(socket), error_(0) { + bool outgoing = (socket_ == 0); + if (outgoing) { + socket_ = static_cast<talk_base::AsyncTCPSocket *>(port->CreatePacketSocket( + (candidate.protocol() == "ssltcp") ? PROTO_SSLTCP : PROTO_TCP)); + } else { + // Incoming connections should match the network address + ASSERT(socket_->GetLocalAddress().EqualIPs(port->address_)); + } + socket_->SignalReadPacket.connect(this, &TCPConnection::OnReadPacket); + socket_->SignalClose.connect(this, &TCPConnection::OnClose); + if (outgoing) { + set_connected(false); + talk_base::SocketAddress local_address(port->address_.ip(), 0); + socket_->SignalConnect.connect(this, &TCPConnection::OnConnect); + socket_->Bind(local_address); + socket_->Connect(candidate.address()); + LOG_F(LS_VERBOSE) << "Connecting from " << local_address.ToString() + << " to " << candidate.address().ToString(); + } +} + +TCPConnection::~TCPConnection() { + delete socket_; +} + +int TCPConnection::Send(const void* data, size_t size) { + 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 { + sent_total_bytes_ += sent; + } + return sent; +} + +int TCPConnection::GetError() { + return error_; +} + +TCPPort* TCPConnection::tcpport() { + return static_cast<TCPPort*>(port_); +} + +void TCPConnection::OnConnect(talk_base::AsyncTCPSocket* socket) { + assert(socket == socket_); + LOG_F(LS_VERBOSE) << "(" << socket->GetRemoteAddress().ToString() << ")"; + set_connected(true); +} + +void TCPConnection::OnClose(talk_base::AsyncTCPSocket* socket, int error) { + assert(socket == socket_); + LOG_F(LS_VERBOSE) << "(" << error << ")"; + set_connected(false); + set_write_state(STATE_WRITE_TIMEOUT); +} + +void TCPConnection::OnReadPacket(const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + assert(socket == socket_); + //LOG_F(LS_INFO) << "(" << size << ", " << remote_addr.ToString() << ")"; + Connection::OnReadPacket(data, size); +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/tcpport.h b/third_party/libjingle/files/talk/p2p/base/tcpport.h new file mode 100644 index 0000000..24555ae --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/tcpport.h @@ -0,0 +1,122 @@ +/* + * 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 __TCPPORT_H__ +#define __TCPPORT_H__ + +#include <list> +#include "talk/base/asynctcpsocket.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: + TCPPort(talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, const talk_base::SocketAddress& address); + 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: + // Handles sending using the local TCP socket. + virtual int SendTo(const void* data, size_t size, + const talk_base::SocketAddress& addr, bool payload); + + // Creates TCPConnection for incoming sockets + void OnAcceptEvent(talk_base::AsyncSocket* socket); + + talk_base::AsyncSocket* socket() { return socket_; } + +private: + // Note: use this until Network ips are stable, then use network->ip + talk_base::SocketAddress address_; + bool incoming_only_; + talk_base::AsyncSocket* socket_; + int error_; + + struct Incoming { + talk_base::SocketAddress addr; + talk_base::AsyncTCPSocket * socket; + }; + std::list<Incoming> incoming_; + + talk_base::AsyncTCPSocket * GetIncoming(const talk_base::SocketAddress& addr, + bool remove = false); + + // Receives packet signal from the local TCP Socket. + void OnReadPacket(const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + + friend class TCPConnection; +}; + +class TCPConnection : public Connection { +public: + // Connection is outgoing unless socket is specified + TCPConnection(TCPPort* port, const Candidate& candidate, + talk_base::AsyncTCPSocket* socket = 0); + virtual ~TCPConnection(); + + virtual int Send(const void* data, size_t size); + virtual int GetError(); + + talk_base::AsyncTCPSocket * socket() { return socket_; } + +private: + TCPPort* tcpport(); + talk_base::AsyncTCPSocket* socket_; + int error_; + + void OnConnect(talk_base::AsyncTCPSocket* socket); + void OnClose(talk_base::AsyncTCPSocket* socket, int error); + void OnReadPacket(const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); + + friend class TCPPort; +}; + +} // namespace cricket + +#endif // __TCPPORT_H__ diff --git a/third_party/libjingle/files/talk/p2p/base/transport.cc b/third_party/libjingle/files/talk/p2p/base/transport.cc new file mode 100644 index 0000000..dfa2dfd --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/transport.cc @@ -0,0 +1,441 @@ +/* + * 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/p2p/base/transport.h" +#include "talk/p2p/base/sessionmanager.h" +#include "talk/p2p/base/transportchannelimpl.h" +#include "talk/p2p/base/constants.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmpp/constants.h" + +namespace { + +struct ChannelParams { + std::string name; + std::string session_type; + cricket::TransportChannelImpl* channel; + buzz::XmlElement* elem; + + ChannelParams() : channel(NULL), elem(NULL) {} +}; +typedef talk_base::TypedMessageData<ChannelParams*> ChannelMessage; + +const int MSG_CREATECHANNEL = 1; +const int MSG_DESTROYCHANNEL = 2; +const int MSG_DESTROYALLCHANNELS = 3; +const int MSG_CONNECTCHANNELS = 4; +const int MSG_RESETCHANNELS = 5; +const int MSG_ONSIGNALINGREADY = 6; +const int MSG_FORWARDCHANNELMESSAGE = 7; +const int MSG_READSTATE = 8; +const int MSG_WRITESTATE = 9; +const int MSG_REQUESTSIGNALING = 10; +const int MSG_ONCHANNELMESSAGE = 11; +const int MSG_CONNECTING = 12; + +} // namespace + +namespace cricket { + +Transport::Transport(SessionManager* session_manager, const std::string& name) + : session_manager_(session_manager), name_(name), destroyed_(false), + readable_(false), writable_(false), connect_requested_(false), + allow_local_ips_(false) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); +} + +Transport::~Transport() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(destroyed_); +} + +TransportChannelImpl* Transport::CreateChannel(const std::string& name, const std::string &session_type) { + ChannelParams params; + params.name = name; + params.session_type = session_type; + ChannelMessage msg(¶ms); + session_manager_->worker_thread()->Send(this, MSG_CREATECHANNEL, &msg); + return msg.data()->channel; +} + +TransportChannelImpl* Transport::CreateChannel_w(const std::string& name, const std::string &session_type) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + + TransportChannelImpl* impl = CreateTransportChannel(name, session_type); + impl->SignalReadableState.connect(this, &Transport::OnChannelReadableState); + impl->SignalWritableState.connect(this, &Transport::OnChannelWritableState); + impl->SignalRequestSignaling.connect( + this, &Transport::OnChannelRequestSignaling); + impl->SignalChannelMessage.connect(this, &Transport::OnChannelMessage); + + 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. + session_manager_->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; + params.name = name; + ChannelMessage msg(¶ms); + session_manager_->worker_thread()->Send(this, MSG_DESTROYCHANNEL, &msg); +} + +void Transport::DestroyChannel_w(const std::string& name) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + TransportChannelImpl* impl = NULL; + { + talk_base::CritScope cs(&crit_); + ChannelMap::iterator iter = channels_.find(name); + ASSERT(iter != channels_.end()); + impl = iter->second; + channels_.erase(iter); + } + + if (connect_requested_ && channels_.empty()) { + // We're not longer attempting to connect. + session_manager_->signaling_thread()->Post(this, MSG_CONNECTING, NULL); + } + + if (impl) + DestroyTransportChannel(impl); +} + +void Transport::ConnectChannels() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + session_manager_->worker_thread()->Post(this, MSG_CONNECTCHANNELS, NULL); +} + +void Transport::ConnectChannels_w() { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + if (connect_requested_) + return; + connect_requested_ = true; + session_manager_->signaling_thread()->Post(this, MSG_ONCHANNELMESSAGE, NULL); + CallChannels_w(&TransportChannelImpl::Connect); + if (!channels_.empty()) { + session_manager_->signaling_thread()->Post(this, MSG_CONNECTING, NULL); + } +} + +void Transport::OnConnecting_s() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + SignalConnecting(this); +} + +void Transport::DestroyAllChannels() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + session_manager_->worker_thread()->Send(this, MSG_DESTROYALLCHANNELS, NULL); + destroyed_ = true; +} + +void Transport::DestroyAllChannels_w() { + ASSERT(session_manager_->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(session_manager_->signaling_thread()->IsCurrent()); + session_manager_->worker_thread()->Post(this, MSG_RESETCHANNELS, NULL); +} + +void Transport::ResetChannels_w() { + ASSERT(session_manager_->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_); + for (size_t i=0; i<messages_.size(); ++i) { + delete messages_[i]; + } + messages_.clear(); + + // Reset all of the channels + CallChannels_w(&TransportChannelImpl::Reset); +} + +void Transport::OnSignalingReady() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + session_manager_->worker_thread()->Post(this, MSG_ONSIGNALINGREADY, NULL); + + // Notify the subclass. + OnTransportSignalingReady(); +} + +void Transport::CallChannels_w(TransportChannelFunc func) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + talk_base::CritScope cs(&crit_); + for (ChannelMap::iterator iter = channels_.begin(); + iter != channels_.end(); + ++iter) { + ((iter->second)->*func)(); + } +} + +void Transport::ForwardChannelMessage(const std::string& name, + buzz::XmlElement* elem) { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(HasChannel(name)); + ChannelParams* params = new ChannelParams(); + params->name = name; + params->elem = elem; + ChannelMessage* msg = new ChannelMessage(params); + session_manager_->worker_thread()->Post(this, MSG_FORWARDCHANNELMESSAGE, msg); +} + +void Transport::ForwardChannelMessage_w(const std::string& name, + buzz::XmlElement* elem) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + ChannelMap::iterator iter = channels_.find(name); + // It's ok for a channel to go away while this message is in transit. + if (iter != channels_.end()) { + iter->second->OnChannelMessage(elem); + } + delete elem; +} + +void Transport::OnChannelReadableState(TransportChannel* channel) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + session_manager_->signaling_thread()->Post(this, MSG_READSTATE, NULL); +} + +void Transport::OnChannelReadableState_s() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + bool readable = GetTransportState_s(true); + if (readable_ != readable) { + readable_ = readable; + SignalReadableState(this); + } +} + +void Transport::OnChannelWritableState(TransportChannel* channel) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + session_manager_->signaling_thread()->Post(this, MSG_WRITESTATE, NULL); +} + +void Transport::OnChannelWritableState_s() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + bool writable = GetTransportState_s(false); + if (writable_ != writable) { + writable_ = writable; + SignalWritableState(this); + } +} + +bool Transport::GetTransportState_s(bool read) { + ASSERT(session_manager_->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(session_manager_->worker_thread()->IsCurrent()); + session_manager_->signaling_thread()->Post(this, MSG_REQUESTSIGNALING, NULL); +} + +void Transport::OnChannelRequestSignaling_s() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + SignalRequestSignaling(this); +} + +void Transport::OnChannelMessage(TransportChannelImpl* impl, + buzz::XmlElement* elem) { + ASSERT(session_manager_->worker_thread()->IsCurrent()); + talk_base::CritScope cs(&crit_); + messages_.push_back(elem); + + // We hold any messages until the client lets us connect. + if (connect_requested_) { + session_manager_->signaling_thread()->Post( + this, MSG_ONCHANNELMESSAGE, NULL); + } +} + +void Transport::OnChannelMessage_s() { + ASSERT(session_manager_->signaling_thread()->IsCurrent()); + ASSERT(connect_requested_); + + std::vector<buzz::XmlElement*> msgs; + { + talk_base::CritScope cs(&crit_); + msgs.swap(messages_); + } + + if (!msgs.empty()) + OnTransportChannelMessages(msgs); +} + +void Transport::OnTransportChannelMessages( + const std::vector<buzz::XmlElement*>& msgs) { + std::vector<buzz::XmlElement*> elems; + for (size_t i = 0; i < msgs.size(); ++i) { + buzz::XmlElement* elem = + new buzz::XmlElement(buzz::QName(name(), "transport")); + elem->AddElement(msgs[i]); + elems.push_back(elem); + } + SignalTransportMessage(this, elems); +} + +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->session_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_FORWARDCHANNELMESSAGE: + { + ChannelParams* params = static_cast<ChannelMessage*>(msg->pdata)->data(); + ForwardChannelMessage_w(params->name, params->elem); + delete params; + } + 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_ONCHANNELMESSAGE: + OnChannelMessage_s(); + break; + } +} + +bool Transport::BadRequest(const buzz::XmlElement* stanza, + const std::string& text, + const buzz::XmlElement* extra_info) { + SignalTransportError(this, stanza, buzz::QN_STANZA_BAD_REQUEST, "modify", + text, extra_info); + return false; +} + +bool Transport::ParseAddress(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + talk_base::SocketAddress* address) { + ASSERT(elem->HasAttr(QN_ADDRESS)); + ASSERT(elem->HasAttr(QN_PORT)); + + // Record the parts of the address. + address->SetIP(elem->Attr(QN_ADDRESS)); + std::istringstream ist(elem->Attr(QN_PORT)); + int port; + ist >> port; + address->SetPort(port); + + // No address zero. + if (address->IsAny()) + return BadRequest(stanza, "candidate has address of zero", NULL); + + // Always disallow addresses that refer to the local host. + if (address->IsLocalIP() && !allow_local_ips_) + return BadRequest(stanza, "candidate has local IP address", NULL); + + // Disallow all ports below 1024, except for 80 and 443 on public addresses. + if (port < 1024) { + if ((port != 80) && (port != 443)) + return BadRequest(stanza, + "candidate has port below 1024, but not 80 or 443", + NULL); + if (address->IsPrivateIP()) { + return BadRequest(stanza, "candidate has port of 80 or 443 with private " + "IP address", NULL); + } + } + + return true; +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/transport.h b/third_party/libjingle/files/talk/p2p/base/transport.h new file mode 100644 index 0000000..826fd26 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/transport.h @@ -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. + */ + +// 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., ForwardChannelMessage). +// +// 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 _CRICKET_P2P_BASE_TRANSPORT_H_ +#define _CRICKET_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" + +namespace buzz { +class QName; +class XmlElement; +} + +namespace cricket { + +class SessionManager; +class Session; +class TransportChannel; +class TransportChannelImpl; + +class Transport : public talk_base::MessageHandler, public sigslot::has_slots<> { + public: + Transport(SessionManager* session_manager, const std::string& name); + virtual ~Transport(); + + // Returns a pointer to the singleton session manager. + SessionManager* session_manager() const { return session_manager_; } + + // Returns the name of this transport. + const std::string& name() const { return name_; } + + // 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 &session_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(); + + // The session handshake includes negotiation of both the application and the + // transport. The initiating transport creates an "offer" describing what + // options it supports and the responding transport creates an "answer" + // describing which options it has accepted. If OnTransport* returns false, + // that indicates that no acceptable options given and this transport cannot + // be negotiated. + // + // The transport negotiation operates as follows. When the initiating client + // creates the session, but before they send the initiate, we create the + // supported transports. The client may override these, but otherwise they + // get a default set. When the initiate is sent, we ask each transport to + // produce an offer. When the receiving client gets the initiate, they will + // iterate through the transport offers in order of their own preference. + // For each one, they create the transport (if they know what it is) and + // call OnTransportOffer. If this returns true, then we're good; otherwise, + // we continue iterating. If no transport works, then we reject the session. + // Otherwise, we have a single transport, and we send back a transport-accept + // message that contains the answer. When this arrives at the initiating + // client, we destroy all transports but the one in the answer and then pass + // the answer to it. If this transport cannot be found or it cannot accept + // the answer, then we reject the session. Otherwise, we're in good shape. + virtual buzz::XmlElement* CreateTransportOffer() = 0; + virtual buzz::XmlElement* CreateTransportAnswer() = 0; + virtual bool OnTransportOffer(const buzz::XmlElement* elem) = 0; + virtual bool OnTransportAnswer(const buzz::XmlElement* elem) = 0; + + // 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 and receiving of stanzas related to negotiating the + // connections of the channels. Different transports may have very different + // signaling, so the XML is handled by the subclass. The msg variable holds + // the element whose name matches this transport, while stanza holds the + // entire stanza. The latter is needed when sending an error response. + // SignalTransportMessage is given the elements that will become the children + // of the transport-info message. Each element must have a name that matches + // the transport's name. + virtual bool OnTransportMessage(const buzz::XmlElement* msg, + const buzz::XmlElement* stanza) = 0; + sigslot::signal2<Transport*, const std::vector<buzz::XmlElement*>&> + SignalTransportMessage; + + // 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. + virtual bool OnTransportError(const buzz::XmlElement* session_msg, + const buzz::XmlElement* error) = 0; + 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: + // Helper function to bad-request error for a stanza passed to + // OnTransportMessage. Returns false. + bool BadRequest(const buzz::XmlElement* stanza, const std::string& text, + const buzz::XmlElement* extra_info); + + // Helper function to parse an element describing an address. This retrieves + // the IP and port from the given element (using QN_ADDRESS and QN_PORT) and + // verifies that they look like plausible values. + bool ParseAddress(const buzz::XmlElement* stanza, + const buzz::XmlElement* elem, + talk_base::SocketAddress* address); + + // 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 &session_type) = 0; + virtual void DestroyTransportChannel(TransportChannelImpl* channel) = 0; + + // Informs the subclass that we received the signaling ready message. + virtual void OnTransportSignalingReady() {} + + // Forwards the given XML element to the channel on the worker thread. This + // occurs asynchronously, so we take ownership of the element. Furthermore, + // the channel will not be able to return an error if the XML is invalid, so + // the transport should have checked its validity already. + void ForwardChannelMessage(const std::string& name, + buzz::XmlElement* elem); + + // Handles a set of messages sent by the channels. The default + // implementation simply forwards each as its own transport message by + // wrapping it in an element identifying this transport and then invoking + // SignalTransportMessage. Smarter transports may be able to place multiple + // channel messages within one transport message. + // + // Note: The implementor of this method is responsible for deleting the XML + // elements passed in, unless they are sent to SignalTransportMessage, where + // the receiver will delete them. + virtual void OnTransportChannelMessages( + const std::vector<buzz::XmlElement*>& msgs); + + private: + typedef std::map<std::string, TransportChannelImpl*> ChannelMap; + typedef std::vector<buzz::XmlElement*> XmlElementList; + + SessionManager* session_manager_; + std::string name_; + bool destroyed_; + bool readable_; + bool writable_; + bool connect_requested_; + ChannelMap channels_; + XmlElementList messages_; + talk_base::CriticalSection crit_; // Protects changes to channels and messages + bool allow_local_ips_; + + // 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 channel wishes to send a transport message. + void OnChannelMessage(TransportChannelImpl* impl, buzz::XmlElement* elem); + + // 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& session_type); + void DestroyChannel_w(const std::string& name); + void ConnectChannels_w(); + void ResetChannels_w(); + void DestroyAllChannels_w(); + void ForwardChannelMessage_w(const std::string& name, + buzz::XmlElement* elem); + 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); + + // Invoked when there are messages waiting to send in the messages_ list. + // We wait to send any messages until the client asks us to connect. + void OnChannelMessage_s(); + + DISALLOW_EVIL_CONSTRUCTORS(Transport); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_TRANSPORT_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/transportchannel.cc b/third_party/libjingle/files/talk/p2p/base/transportchannel.cc new file mode 100644 index 0000000..ba076ac --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/p2p/base/transportchannel.h b/third_party/libjingle/files/talk/p2p/base/transportchannel.h new file mode 100644 index 0000000..68b8fd1 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/transportchannel.h @@ -0,0 +1,104 @@ +/* + * 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_TRANSPORTCHANNEL_H_ +#define _CRICKET_P2P_BASE_TRANSPORTCHANNEL_H_ + +#include <string> +#include "talk/base/basictypes.h" +#include "talk/base/sigslot.h" +#include "talk/base/socket.h" + +namespace cricket { + +// 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 &session_type) + : name_(name), session_type_(session_type), readable_(false), writable_(false) {} + virtual ~TransportChannel() {} + + // Returns the name of this channel. + const std::string& name() const { return name_; } + const std::string& session_type() const { return session_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; + + // 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; + + // TODO: Generalize network monitoring. + + // 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 session_type_; + bool readable_; + bool writable_; + + DISALLOW_EVIL_CONSTRUCTORS(TransportChannel); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_TRANSPORTCHANNEL_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/transportchannelimpl.h b/third_party/libjingle/files/talk/p2p/base/transportchannelimpl.h new file mode 100644 index 0000000..742d307 --- /dev/null +++ b/third_party/libjingle/files/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 _CRICKET_P2P_BASE_TRANSPORTCHANNELIMPL_H_ +#define _CRICKET_P2P_BASE_TRANSPORTCHANNELIMPL_H_ + +#include <string> +#include "talk/p2p/base/transportchannel.h" + +namespace buzz { class XmlElement; } + +namespace cricket { + +class Transport; + +// 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& session_type) + : TransportChannel(name, session_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 stanzas related to this particular + // channel. Any channel may send whatever messages it wants. The Transport + // receives all incoming messages and may forward them to the relevant + // channel. The transport will delete signaled messages. + // + // Note: Since these messages 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. + virtual void OnChannelMessage(const buzz::XmlElement* msg) = 0; + sigslot::signal2<TransportChannelImpl*, + buzz::XmlElement*> SignalChannelMessage; + + private: + DISALLOW_EVIL_CONSTRUCTORS(TransportChannelImpl); +}; + +} // namespace cricket + +#endif // _CRICKET_P2P_BASE_TRANSPORTCHANNELIMPL_H_ diff --git a/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.cc b/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.cc new file mode 100644 index 0000000..bc94d67 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.cc @@ -0,0 +1,99 @@ +/* + * 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 &session_type) + : TransportChannel(name, session_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) { + ASSERT(impl_ != NULL); // should not be used until channel is writable + return impl_->SendPacket(data, len); +} + +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(); +} + +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/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.h b/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.h new file mode 100644 index 0000000..46d8a44 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/transportchannelproxy.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 _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 &session_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(); + + 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/third_party/libjingle/files/talk/p2p/base/udpport.cc b/third_party/libjingle/files/talk/p2p/base/udpport.cc new file mode 100644 index 0000000..fa47eba9 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/udpport.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. + */ + +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#include "talk/base/logging.h" +#include "talk/p2p/base/udpport.h" +#include <iostream> +#include <cassert> + +#if defined(_MSC_VER) && _MSC_VER < 1300 +namespace std { + using ::strerror; +} +#endif + +#ifdef POSIX +extern "C" { +#include <errno.h> +} +#endif // POSIX + +namespace cricket { + +const std::string LOCAL_PORT_TYPE("local"); + +UDPPort::UDPPort(talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, + const talk_base::SocketAddress& address) + : Port(thread, LOCAL_PORT_TYPE, factory, network), error_(0) { + socket_ = CreatePacketSocket(PROTO_UDP); + socket_->SignalReadPacket.connect(this, &UDPPort::OnReadPacketSlot); + if (socket_->Bind(address) < 0) + PLOG(LERROR, socket_->GetError()) << "bind"; +} + +UDPPort::UDPPort(talk_base::Thread* thread, const std::string &type, + talk_base::SocketFactory* factory, talk_base::Network* network) + : Port(thread, type, factory, network), socket_(0), error_(0) { +} + +UDPPort::~UDPPort() { + delete socket_; +} + +void UDPPort::PrepareAddress() { + assert(socket_); + AddAddress(socket_->GetLocalAddress(), "udp", true); +} + +Connection* UDPPort::CreateConnection(const Candidate& address, + CandidateOrigin origin) { + if (address.protocol() != "udp") + return 0; + + 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) { + assert(socket_); + int sent = socket_->SendTo(data, size, addr); + if (sent < 0) + error_ = socket_->GetError(); + return sent; +} + +int UDPPort::SetOption(talk_base::Socket::Option opt, int value) { + return socket_->SetOption(opt, value); +} + +int UDPPort::GetError() { + assert(socket_); + return error_; +} + +void UDPPort::OnReadPacketSlot( + const char* data, size_t size, const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket) { + assert(socket == socket_); + OnReadPacket(data, size, remote_addr); +} + +void UDPPort::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); + } +} + +} // namespace cricket diff --git a/third_party/libjingle/files/talk/p2p/base/udpport.h b/third_party/libjingle/files/talk/p2p/base/udpport.h new file mode 100644 index 0000000..9fcfa69 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/base/udpport.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 __UDPPORT_H__ +#define __UDPPORT_H__ + +#include "talk/base/asyncudpsocket.h" +#include "talk/p2p/base/port.h" + +namespace talk_base { + class Thread; + class Network; + class SocketAddress; +} // namespace talk_base + +namespace cricket { + +extern const std::string LOCAL_PORT_TYPE; // type of UDP ports + +// Communicates using a local UDP 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 UDPPort::OnReadPacket (3 arg) to dispatch to a connection. +class UDPPort : public Port { +public: + UDPPort(talk_base::Thread* thread, talk_base::SocketFactory* factory, + talk_base::Network* network, const talk_base::SocketAddress& address); + 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, const std::string &type, + talk_base::SocketFactory* factory, talk_base::Network* network); + + // Handles sending using the local UDP socket. + 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); + + talk_base::AsyncPacketSocket* socket() { return socket_; } + +private: + talk_base::AsyncPacketSocket* socket_; + int error_; + + // Receives packet signal from the local UDP Socket. + void OnReadPacketSlot( + const char* data, size_t size, + const talk_base::SocketAddress& remote_addr, + talk_base::AsyncPacketSocket* socket); +}; + +} // namespace cricket + +#endif // __UDPPORT_H__ diff --git a/third_party/libjingle/files/talk/p2p/client/Makefile.am b/third_party/libjingle/files/talk/p2p/client/Makefile.am new file mode 100644 index 0000000..11447f7 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/Makefile.am @@ -0,0 +1,13 @@ +libcricketp2pclient_la_SOURCES = basicportallocator.cc \ + httpportallocator.cc \ + socketmonitor.cc + +noinst_HEADERS = basicportallocator.h \ + socketmonitor.h \ + sessionmanagertask.h \ + sessionsendtask.h \ + httpportallocator.h + +AM_CPPFLAGS = -I../../.. -DLINUX -DPOSIX -DINTERNAL_BUILD + +noinst_LTLIBRARIES = libcricketp2pclient.la diff --git a/third_party/libjingle/files/talk/p2p/client/basicportallocator.cc b/third_party/libjingle/files/talk/p2p/client/basicportallocator.cc new file mode 100644 index 0000000..b7960f4 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/basicportallocator.cc @@ -0,0 +1,690 @@ +/* + * 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/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" + +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; + +const float RELAY_PRIMARY_PREF_MODIFIER = 0.0f; // modifiers of the above constants +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 + cricket::CreateRandomId() % range; +} + +} + +namespace cricket { + +// 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); + ~AllocationSequence(); + + // Determines whether this sequence is operating on an equivalent network + // setup to the one given. + bool IsEquivalent(talk_base::Network* network); + + // 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: + BasicPortAllocatorSession* session_; + talk_base::Network* network_; + uint32 ip_; + PortConfiguration* config_; + bool running_; + int step_; + int step_of_phase_[kNumPhases]; + + typedef std::vector<ProtocolType> ProtocolList; + ProtocolList protocols_; + + void CreateUDPPorts(); + void CreateTCPPorts(); + void CreateStunPorts(); + void CreateRelayPorts(); +}; + + +// BasicPortAllocator + +BasicPortAllocator::BasicPortAllocator( + talk_base::NetworkManager* network_manager) + : network_manager_(network_manager), best_writable_phase_(-1), + stun_address_(NULL), relay_address_(NULL) { +} + +BasicPortAllocator::BasicPortAllocator( + talk_base::NetworkManager* network_manager, + talk_base::SocketAddress* stun_address, + talk_base::SocketAddress *relay_address) + : network_manager_(network_manager), best_writable_phase_(-1), + stun_address_(stun_address), relay_address_(relay_address) { +} + +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, stun_address_, + relay_address_); +} + +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), network_thread_(NULL), session_type_(session_type), + allocation_started_(false), running_(false), stun_address_(NULL), + relay_address_(NULL) { +} + +BasicPortAllocatorSession::BasicPortAllocatorSession( + BasicPortAllocator *allocator, + const std::string &name, + const std::string &session_type, + talk_base::SocketAddress *stun_address, + talk_base::SocketAddress *relay_address) + : PortAllocatorSession(allocator->flags()), allocator_(allocator), + name_(name), session_type_(session_type), network_thread_(NULL), + allocation_started_(false), running_(false), stun_address_(stun_address), + relay_address_(relay_address) { +} + +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 = NULL; + if (stun_address_ != NULL) + config = new PortConfiguration(*stun_address_, + CreateRandomString(16), + CreateRandomString(16), + ""); + PortConfiguration::PortList ports; + if (relay_address_ != NULL) { + ports.push_back(ProtocolAddress(*relay_address_, PROTO_UDP)); + 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_); + + if (allocator_->proxy().type != talk_base::PROXY_NONE) + Port::set_proxy(allocator_->user_agent(), allocator_->proxy()); + + 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; + allocator_->network_manager()->GetNetworks(networks); + + for (uint32 i = 0; i < networks.size(); ++i) { + if (HasEquivalentSequence(networks[i])) + continue; + + PortConfiguration* config = NULL; + if (configs_.size() > 0) + config = configs_.back(); + + AllocationSequence* sequence = + new AllocationSequence(this, networks[i], config); + if (running_) + sequence->Start(); + + sequences_.push_back(sequence); + } + + allocation_started_ = true; + if (running_) + network_thread_->PostDelayed(ALLOCATE_DELAY, this, MSG_ALLOCATE); +} + +bool BasicPortAllocatorSession::HasEquivalentSequence( + talk_base::Network* network) { + for (uint32 i = 0; i < sequences_.size(); ++i) + if (sequences_[i]->IsEquivalent(network)) + return true; + return false; +} + +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()); + 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 = + 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 " << (int)ports.size() << " ports and " + << (int)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) + : session_(session), network_(network), ip_(network->ip()), config_(config), + running_(false), step_(0) { + + // 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); +} + +bool AllocationSequence::IsEquivalent(talk_base::Network* network) { + return (network == network_) && (ip_ == network->ip()); +} + +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 (session_->flags() & PORTALLOCATOR_DISABLE_UDP) + return; + + Port* port = new UDPPort(session_->network_thread(), NULL, network_, + talk_base::SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_UDP); +} + +void AllocationSequence::CreateTCPPorts() { + if (session_->flags() & PORTALLOCATOR_DISABLE_TCP) + return; + + Port* port = new TCPPort(session_->network_thread(), NULL, network_, + talk_base::SocketAddress(ip_, 0)); + session_->AddAllocatedPort(port, this, PREF_LOCAL_TCP); +} + +void AllocationSequence::CreateStunPorts() { + if (session_->flags() & PORTALLOCATOR_DISABLE_STUN) + return; + + if (!config_ || config_->stun_address.IsAny()) + return; + + Port* port = new StunPort(session_->network_thread(), NULL, network_, + talk_base::SocketAddress(ip_, 0), + config_->stun_address); + session_->AddAllocatedPort(port, this, PREF_LOCAL_STUN); +} + +void AllocationSequence::CreateRelayPorts() { + if (session_->flags() & PORTALLOCATOR_DISABLE_RELAY) + return; + + if (!config_) + return; + + PortConfiguration::RelayList::const_iterator relay; + for (relay = config_->relays.begin(); + relay != config_->relays.end(); + ++relay) { + + RelayPort *port = new RelayPort(session_->network_thread(), NULL, network_, + talk_base::SocketAddress(ip_, 0), + config_->username, config_->password, + config_->magic_cookie); + // 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::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/third_party/libjingle/files/talk/p2p/client/basicportallocator.h b/third_party/libjingle/files/talk/p2p/client/basicportallocator.h new file mode 100644 index 0000000..0e3d313 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/basicportallocator.h @@ -0,0 +1,175 @@ +/* + * 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 _BASICPORTALLOCATOR_H_ +#define _BASICPORTALLOCATOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/messagequeue.h" +#include "talk/base/network.h" +#include "talk/p2p/base/portallocator.h" +#include <string> +#include <vector> + +namespace cricket { + +class BasicPortAllocator : public PortAllocator { +public: + BasicPortAllocator(talk_base::NetworkManager* network_manager); + BasicPortAllocator(talk_base::NetworkManager* network_manager, + talk_base::SocketAddress *stun_server, talk_base::SocketAddress *relay_server); + virtual ~BasicPortAllocator(); + + talk_base::NetworkManager* network_manager() { return network_manager_; } + + // 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); + +private: + talk_base::NetworkManager* network_manager_; + talk_base::SocketAddress* stun_address_; + talk_base::SocketAddress* relay_address_; + int best_writable_phase_; +}; + +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(BasicPortAllocator *allocator, + const std::string &name, + const std::string &session_type, + talk_base::SocketAddress *stun_address, + talk_base::SocketAddress *relay_address); + ~BasicPortAllocatorSession(); + + 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(); + bool HasEquivalentSequence(talk_base::Network* network); + 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_; + talk_base::SocketAddress *stun_address_; + talk_base::SocketAddress *relay_address_; + + 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); + + // Determines whether the given relay server supports the given protocol. + static bool SupportsProtocol(const PortConfiguration::RelayServer& relay, + ProtocolType type); +}; + +} // namespace cricket + +#endif // _BASICPORTALLOCATOR_H_ diff --git a/third_party/libjingle/files/talk/p2p/client/httpportallocator.cc b/third_party/libjingle/files/talk/p2p/client/httpportallocator.cc new file mode 100644 index 0000000..17b1566 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/httpportallocator.cc @@ -0,0 +1,185 @@ +#if defined(_MSC_VER) && _MSC_VER < 1300 +#pragma warning(disable:4786) +#endif +#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/signalthread.h" +#include "talk/p2p/client/httpportallocator.h" +#include <cassert> +#include <ctime> + +namespace { + +// Records the port on the hosts that will receive HTTP requests. +const uint16 kHostPort = 80; + +// Records the URL that we will GET in order to create a session. +const std::string kCreateSessionURL = "/create_session"; + +// The number of HTTP requests we should attempt before giving up. +const size_t kNumRetries = 5; + +// The delay before we give up on an HTTP request; +const int TIMEOUT = 5 * 1000; // 5 seconds + +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; + } + + size_t last = str.find_last_not_of(" \t\r\n"); + ASSERT(last != 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 cricket { + +// HttpPortAllocator + +HttpPortAllocator::HttpPortAllocator(talk_base::NetworkManager* network_manager, const std::string &user_agent) + : BasicPortAllocator(network_manager), 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), + attempts_(0), relay_hosts_(relay_hosts), stun_hosts_(stun_hosts), relay_token_(relay_token), agent_(user_agent) { +} + +void HttpPortAllocatorSession::GetPortConfigurations() { + + if (attempts_ == kNumRetries) { + LOG(WARNING) << "HttpPortAllocator: maximum number of requests reached"; + return; + } + + // Choose the next host to try. + std::string host = relay_hosts_[attempts_ % relay_hosts_.size()]; + attempts_++; + LOG(INFO) << "HTTPPortAllocator: sending to host " << host; + + // 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 = 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->set_host(host); + request->set_port(kHostPort); + 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(WARNING) << "HTTPPortAllocator: request " + << " received error " << request->response().scode; + GetPortConfigurations(); + return; + } + LOG(INFO) << "HTTPPortAllocator: request succeeded"; + + StringMap map; + 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); + ParseMap(resp, 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/third_party/libjingle/files/talk/p2p/client/httpportallocator.h b/third_party/libjingle/files/talk/p2p/client/httpportallocator.h new file mode 100644 index 0000000..96f3e72 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/httpportallocator.h @@ -0,0 +1,61 @@ +#ifndef _HTTPPORTALLOCATOR_H_ +#define _HTTPPORTALLOCATOR_H_ + +#include "talk/p2p/client/basicportallocator.h" + +namespace talk_base { + class SignalThread; +} + +namespace cricket { + +class HttpPortAllocator : public BasicPortAllocator { +public: + HttpPortAllocator(talk_base::NetworkManager* network_manager, 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) {stun_hosts_ = hosts;} + void SetRelayHosts(const std::vector<std::string> &hosts) {relay_hosts_ = hosts;} + void SetRelayToken(const std::string &relay) {relay_token_ = relay;} + std::string relay_token() const { return relay_token_; } +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); + ~HttpPortAllocatorSession() {}; + +protected: + virtual void GetPortConfigurations(); + +private: + std::vector<std::string> relay_hosts_; + std::vector<talk_base::SocketAddress> stun_hosts_; + std::string relay_token_; + std::string agent_; + + void OnRequestDone(talk_base::SignalThread* request); + HttpPortAllocator* http_allocator() { + return static_cast<HttpPortAllocator*>(allocator()); + } + int attempts_; +}; + +} // namespace cricket + +#endif // _XMPPPORTALLOCATOR_H_ diff --git a/third_party/libjingle/files/talk/p2p/client/sessionmanagertask.h b/third_party/libjingle/files/talk/p2p/client/sessionmanagertask.h new file mode 100644 index 0000000..2a05357 --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/sessionmanagertask.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 _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(Task *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(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/third_party/libjingle/files/talk/p2p/client/sessionsendtask.h b/third_party/libjingle/files/talk/p2p/client/sessionsendtask.h new file mode 100644 index 0000000..3beb9d4 --- /dev/null +++ b/third_party/libjingle/files/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(Task *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/third_party/libjingle/files/talk/p2p/client/socketmonitor.cc b/third_party/libjingle/files/talk/p2p/client/socketmonitor.cc new file mode 100644 index 0000000..88d37ce --- /dev/null +++ b/third_party/libjingle/files/talk/p2p/client/socketmonitor.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. + */ + +#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(Session* session, + TransportChannel* channel, + talk_base::Thread *monitor_thread) { + session_ = session; + channel_ = channel; + channel_thread_ = session->session_manager()->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 = reinterpret_cast<void *>(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); +} + +P2PTransportChannel* SocketMonitor::GetP2PChannel() { + if (session_->transport() == NULL) + return NULL; + if (session_->transport()->name() != kNsP2pTransport) + return NULL; + TransportChannelImpl* impl = session_->GetImplementation(channel_); + if (impl == NULL) + return NULL; + return static_cast<P2PTransportChannel*>(impl); +} + +} diff --git a/third_party/libjingle/files/talk/p2p/client/socketmonitor.h b/third_party/libjingle/files/talk/p2p/client/socketmonitor.h new file mode 100644 index 0000000..ced86b8 --- /dev/null +++ b/third_party/libjingle/files/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 _SOCKETMONITOR_H_ +#define _SOCKETMONITOR_H_ + +#include "talk/base/thread.h" +#include "talk/base/sigslot.h" +#include "talk/base/criticalsection.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/p2ptransportchannel.h" +#include "talk/p2p/base/port.h" +#include <vector> + +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(Session* session, TransportChannel* channel, + 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: + void OnMessage(talk_base::Message *message); + void OnConnectionMonitor(P2PTransportChannel* channel); + void PollSocket(bool poll); + P2PTransportChannel* GetP2PChannel(); + + std::vector<ConnectionInfo> connection_infos_; + Session* session_; + TransportChannel* channel_; + talk_base::Thread* channel_thread_; + talk_base::Thread* monitoring_thread_; + talk_base::CriticalSection crit_; + uint32 rate_; + bool monitoring_; +}; + +} + +#endif // _SOCKETMONITOR_H_ diff --git a/third_party/libjingle/files/talk/pkg.m4 b/third_party/libjingle/files/talk/pkg.m4 new file mode 100644 index 0000000..c80e0ac --- /dev/null +++ b/third_party/libjingle/files/talk/pkg.m4 @@ -0,0 +1,57 @@ + +dnl PKG_CHECK_MODULES(GSTUFF, gtk+-2.0 >= 1.3 glib = 1.3.4, action-if, action-not) +dnl defines GSTUFF_LIBS, GSTUFF_CFLAGS, see pkg-config man page +dnl also defines GSTUFF_PKG_ERRORS on error +AC_DEFUN(PKG_CHECK_MODULES, [ + succeeded=no + + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + AC_MSG_CHECKING(for $2) + + if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + succeeded=yes + + AC_MSG_CHECKING($1_CFLAGS) + $1_CFLAGS=`$PKG_CONFIG --cflags "$2"` + AC_MSG_RESULT($$1_CFLAGS) + + AC_MSG_CHECKING($1_LIBS) + $1_LIBS=`$PKG_CONFIG --libs "$2"` + AC_MSG_RESULT($$1_LIBS) + else + $1_CFLAGS="" + $1_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + $1_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + ifelse([$4], ,echo $$1_PKG_ERRORS,) + fi + + AC_SUBST($1_CFLAGS) + AC_SUBST($1_LIBS) + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + ifelse([$3], , :, [$3]) + else + ifelse([$4], , AC_MSG_ERROR([Library requirements ($2) not met; consider adjusting the PKG_CONFIG_PATH environment variable if your libraries are in a nonstandard prefix so pkg-config can find them.]), [$4]) + fi +]) + + diff --git a/third_party/libjingle/files/talk/xmllite/Makefile.am b/third_party/libjingle/files/talk/xmllite/Makefile.am new file mode 100644 index 0000000..deaf2bf --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/Makefile.am @@ -0,0 +1,18 @@ +libcricketxmllite_la_SOURCES = qname.cc \ + xmlbuilder.cc \ + xmlconstants.cc \ + xmlelement.cc \ + xmlnsstack.cc \ + xmlparser.cc \ + xmlprinter.cc + +noinst_HEADERS = qname.h \ + xmlbuilder.h \ + xmlconstants.h \ + xmlelement.h \ + xmlnsstack.h \ + xmlparser.h \ + xmlprinter.h +AM_CPPFLAGS = -DPOSIX + +noinst_LTLIBRARIES = libcricketxmllite.la diff --git a/third_party/libjingle/files/talk/xmllite/qname.cc b/third_party/libjingle/files/talk/xmllite/qname.cc new file mode 100644 index 0000000..08f6bd4 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/qname.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. + */ + +#include <string> +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/qname.h" +#include "talk/xmllite/xmlconstants.h" + +//#define new TRACK_NEW + +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/third_party/libjingle/files/talk/xmllite/qname.h b/third_party/libjingle/files/talk/xmllite/qname.h new file mode 100644 index 0000000..b1bcec6 --- /dev/null +++ b/third_party/libjingle/files/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) : + refcount_(1), + namespace_(ns), + localPart_(local) {} + + 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/third_party/libjingle/files/talk/xmllite/xmlbuilder.cc b/third_party/libjingle/files/talk/xmllite/xmlbuilder.cc new file mode 100644 index 0000000..3e2964f --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlbuilder.cc @@ -0,0 +1,149 @@ +/* + * 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/stl_decl.h" +#include <vector> +#include <set> +#include <expat.h> +#include "talk/base/common.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlbuilder.h" + +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/third_party/libjingle/files/talk/xmllite/xmlbuilder.h b/third_party/libjingle/files/talk/xmllite/xmlbuilder.h new file mode 100644 index 0000000..5de31b2 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlbuilder.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 _xmlbuilder_h_ +#define _xmlbuilder_h_ + +#include <string> +#include "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.h" +#include "talk/xmllite/xmlparser.h" + +#include <expat.h> + +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_; + scoped_ptr<XmlElement> pelRoot_; + scoped_ptr<std::vector<XmlElement *, std::allocator<XmlElement *> > > + pvParents_; +}; + +} + +#endif diff --git a/third_party/libjingle/files/talk/xmllite/xmlconstants.cc b/third_party/libjingle/files/talk/xmllite/xmlconstants.cc new file mode 100644 index 0000000..503f832 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmllite/xmlconstants.h b/third_party/libjingle/files/talk/xmllite/xmlconstants.h new file mode 100644 index 0000000..8514d6f --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmllite/xmlelement.cc b/third_party/libjingle/files/talk/xmllite/xmlelement.cc new file mode 100644 index 0000000..ba0a9a0 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlelement.cc @@ -0,0 +1,509 @@ +/* + * 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::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/third_party/libjingle/files/talk/xmllite/xmlelement.h b/third_party/libjingle/files/talk/xmllite/xmlelement.h new file mode 100644 index 0000000..5f8e616a --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlelement.h @@ -0,0 +1,238 @@ +/* + * 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 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/third_party/libjingle/files/talk/xmllite/xmlnsstack.cc b/third_party/libjingle/files/talk/xmllite/xmlnsstack.cc new file mode 100644 index 0000000..4dcb649 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlnsstack.cc @@ -0,0 +1,205 @@ +/* + * 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/stl_decl.h" +#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/third_party/libjingle/files/talk/xmllite/xmlnsstack.h b/third_party/libjingle/files/talk/xmllite/xmlnsstack.h new file mode 100644 index 0000000..299ec1c --- /dev/null +++ b/third_party/libjingle/files/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 "talk/base/scoped_ptr.h" +#include "talk/base/stl_decl.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: + + scoped_ptr<std::vector<std::string, std::allocator<std::string> > > pxmlnsStack_; + scoped_ptr<std::vector<size_t, std::allocator<size_t> > > pxmlnsDepthStack_; +}; +} + +#endif diff --git a/third_party/libjingle/files/talk/xmllite/xmlparser.cc b/third_party/libjingle/files/talk/xmllite/xmlparser.cc new file mode 100644 index 0000000..7486e2d --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlparser.cc @@ -0,0 +1,291 @@ +/* + * 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/stl_decl.h" +#include <string> +#include <vector> +#include <iostream> +#include <expat.h> +#include "talk/base/common.h" +#include "talk/xmllite/xmlconstants.h" +#include "talk/xmllite/xmlelement.h" +#include "talk/xmllite/xmlnsstack.h" +#include "talk/xmllite/xmlparser.h" + +#include <expat.h> + +#define new TRACK_NEW + +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(XML_Size line, XML_Size column, + XML_Index 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/third_party/libjingle/files/talk/xmllite/xmlparser.h b/third_party/libjingle/files/talk/xmllite/xmlparser.h new file mode 100644 index 0000000..2541013 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlparser.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 _xmlparser_h_ +#define _xmlparser_h_ + +#include <string> +#include <expat.h> + +#include "talk/xmllite/xmlnsstack.h" + +struct XML_ParserStruct; +typedef struct XML_ParserStruct* XML_Parser; + +namespace buzz { + +class XmlParseHandler; +class XmlParseContext; +class XmlParser; + +class XmlParseContext { +public: + 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 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(XML_Size line, XML_Size column, XML_Index 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/third_party/libjingle/files/talk/xmllite/xmlprinter.cc b/third_party/libjingle/files/talk/xmllite/xmlprinter.cc new file mode 100644 index 0000000..05c62c8 --- /dev/null +++ b/third_party/libjingle/files/talk/xmllite/xmlprinter.cc @@ -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. + */ + +#include "talk/base/stl_decl.h" +#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/third_party/libjingle/files/talk/xmllite/xmlprinter.h b/third_party/libjingle/files/talk/xmllite/xmlprinter.h new file mode 100644 index 0000000..96900d0 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/Makefile.am b/third_party/libjingle/files/talk/xmpp/Makefile.am new file mode 100644 index 0000000..ad75fbc --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/Makefile.am @@ -0,0 +1,33 @@ +libcricketxmpp_la_SOURCES = constants.cc \ + jid.cc \ + saslmechanism.cc \ + xmppclient.cc \ + xmppengineimpl.cc \ + xmppengineimpl_iq.cc \ + xmpplogintask.cc \ + xmppstanzaparser.cc \ + xmpptask.cc \ + ratelimitmanager.cc + +noinst_HEADERS = asyncsocket.h \ + prexmppauth.h \ + saslhandler.h \ + xmpplogintask.h \ + jid.h \ + saslmechanism.h \ + xmppclient.h \ + constants.h \ + saslplainmechanism.h \ + xmppclientsettings.h \ + xmppstanzaparser.h \ + xmppengine.h \ + xmpptask.h \ + plainsaslhandler.h \ + saslcookiemechanism.h \ + xmppengineimpl.h \ + ratelimitmanager.h + + +AM_CPPFLAGS = -DPOSIX + +noinst_LTLIBRARIES = libcricketxmpp.la diff --git a/third_party/libjingle/files/talk/xmpp/asyncsocket.h b/third_party/libjingle/files/talk/xmpp/asyncsocket.h new file mode 100644 index 0000000..e4bce7f --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/constants.cc b/third_party/libjingle/files/talk/xmpp/constants.cc new file mode 100644 index 0000000..ef9dd9c --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/constants.cc @@ -0,0 +1,402 @@ +/* + * 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 std::string NS_GOOGLE_AUTH_PROTOCOL("http://www.google.com/talk/protocol/auth"); +const QName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT(true, NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result"); + +const std::string NS_GOOGLE_AUTH("google:auth"); +const QName QN_MISSING_USERNAME(true, NS_GOOGLE_AUTH, "missing-username"); +const QName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN(true, NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login"); + +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 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"); + + +// 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"); + + +// 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"); + + + +// 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"); + +} diff --git a/third_party/libjingle/files/talk/xmpp/constants.h b/third_party/libjingle/files/talk/xmpp/constants.h new file mode 100644 index 0000000..95bd1a0 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/constants.h @@ -0,0 +1,361 @@ +/* + * 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_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ +#define _CRICKET_XMPP_XMPPLIB_BUZZ_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 std::string NS_GOOGLE_AUTH; +extern const QName QN_MISSING_USERNAME; +extern const std::string NS_GOOGLE_AUTH_PROTOCOL; +extern const QName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT; +extern const QName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN; + +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; + + +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_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; + + +// 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 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; + +} + +#endif // _CRICKET_XMPP_XMPPLIB_BUZZ_CONSTANTS_H_ diff --git a/third_party/libjingle/files/talk/xmpp/jid.cc b/third_party/libjingle/files/talk/xmpp/jid.cc new file mode 100644 index 0000000..ead2074 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/jid.cc @@ -0,0 +1,506 @@ +/* + * 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. + */ + +extern "C" { +#include <ctype.h> +} +#include <string> +#include "talk/xmpp/jid.h" +#include "talk/xmpp/constants.h" +#include "talk/base/common.h" +#include <algorithm> +#include "talk/base/logging.h" + +namespace buzz { + +static int AsciiToLower(int x) { + return (x <= 'Z' && x >= 'A') ? (x + ('a' - 'A')) : x; +} + +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/third_party/libjingle/files/talk/xmpp/jid.h b/third_party/libjingle/files/talk/xmpp/jid.h new file mode 100644 index 0000000..6831bda --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/plainsaslhandler.h b/third_party/libjingle/files/talk/xmpp/plainsaslhandler.h new file mode 100644 index 0000000..e7d44b9 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/prexmppauth.h b/third_party/libjingle/files/talk/xmpp/prexmppauth.h new file mode 100644 index 0000000..f94bd3d --- /dev/null +++ b/third_party/libjingle/files/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() = 0; + virtual bool IsAuthorized() = 0; + virtual bool HadError() = 0; + virtual int GetError() = 0; + virtual CaptchaChallenge GetCaptchaChallenge() = 0; + virtual std::string GetAuthCookie() = 0; +}; + +} + +#endif diff --git a/third_party/libjingle/files/talk/xmpp/ratelimitmanager.cc b/third_party/libjingle/files/talk/xmpp/ratelimitmanager.cc new file mode 100644 index 0000000..81c55ac --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/ratelimitmanager.cc @@ -0,0 +1,77 @@ +/* + * 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/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/third_party/libjingle/files/talk/xmpp/ratelimitmanager.h b/third_party/libjingle/files/talk/xmpp/ratelimitmanager.h new file mode 100644 index 0000000..79960d8 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/ratelimitmanager.h @@ -0,0 +1,146 @@ +/* + * 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() { + uint32 current_time = talk_base::Time(); + if (talk_base::TimeDiff(current_time, NextTimeAllowedForCounter()) >= 0) { + return true; + } else { + return false; + } + } + + // 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/third_party/libjingle/files/talk/xmpp/saslcookiemechanism.h b/third_party/libjingle/files/talk/xmpp/saslcookiemechanism.h new file mode 100644 index 0000000..a126a4b --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/saslcookiemechanism.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 _SASLCOOKIEMECHANISM_H_ +#define _SASLCOOKIEMECHANISM_H_ + +#include "talk/xmpp/saslmechanism.h" +#include "talk/xmllite/xmlelement.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/third_party/libjingle/files/talk/xmpp/saslhandler.h b/third_party/libjingle/files/talk/xmpp/saslhandler.h new file mode 100644 index 0000000..66386d5 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/saslhandler.h @@ -0,0 +1,60 @@ +/* + * 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/third_party/libjingle/files/talk/xmpp/saslmechanism.cc b/third_party/libjingle/files/talk/xmpp/saslmechanism.cc new file mode 100644 index 0000000..45c947a --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/saslmechanism.cc @@ -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. + */ + +#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); +} + +std::string +SaslMechanism::Base64EncodeFromArray(const char * plain, size_t length) { + return Base64::encodeFromArray(plain, length); +} + +} diff --git a/third_party/libjingle/files/talk/xmpp/saslmechanism.h b/third_party/libjingle/files/talk/xmpp/saslmechanism.h new file mode 100644 index 0000000..f2e5adc --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/saslplainmechanism.h b/third_party/libjingle/files/talk/xmpp/saslplainmechanism.h new file mode 100644 index 0000000..72532e6e --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/xmppclient.cc b/third_party/libjingle/files/talk/xmpp/xmppclient.cc new file mode 100644 index 0000000..0db9b1d4 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppclient.cc @@ -0,0 +1,416 @@ +/* + * 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::Task* 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 + scoped_ptr<AsyncSocket> socket_; + scoped_ptr<XmppEngine> engine_; + 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 + if ((settings.server().IPAsString() == buzz::STR_TALK_GOOGLE_COM || + settings.server().IPAsString() == 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_; +} + +static void +ForgetPassword(std::string & to_erase) { + size_t len = to_erase.size(); + for (size_t i = 0; i < len; i++) { + // get rid of characters + to_erase[i] = 'x'; + } + // get rid of length + to_erase.erase(); +} + +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(); + return XMPP_RETURN_OK; +} + +XmppClient::XmppClient(Task * 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/third_party/libjingle/files/talk/xmpp/xmppclient.h b/third_party/libjingle/files/talk/xmpp/xmppclient.h new file mode 100644 index 0000000..1c4b947 --- /dev/null +++ b/third_party/libjingle/files/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: + XmppClient(talk_base::Task * parent); + ~XmppClient(); + + XmppReturnStatus Connect(const XmppClientSettings & settings, + const std::string & lang, + AsyncSocket * socket, + PreXmppAuth * preauth); + + virtual talk_base::Task* 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 XmppClient::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; + scoped_ptr<Private> d_; + + bool delivering_signal_; + bool valid_; +}; + +} + +#endif diff --git a/third_party/libjingle/files/talk/xmpp/xmppclientsettings.h b/third_party/libjingle/files/talk/xmpp/xmppclientsettings.h new file mode 100644 index 0000000..f5ff0a6 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppclientsettings.h @@ -0,0 +1,103 @@ +/* + * 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 XmppClientSettings { +public: + XmppClientSettings() : + use_tls_(false), use_cookie_auth_(false), protocol_(cricket::PROTO_TCP), + proxy_(talk_base::PROXY_NONE), proxy_port_(80), use_proxy_auth_(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_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; } + 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_; } + 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_; } + 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 use_cookie_auth_; + 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_; + bool allow_plain_; + std::string token_service_; +}; + +} + +#endif diff --git a/third_party/libjingle/files/talk/xmpp/xmppengine.h b/third_party/libjingle/files/talk/xmpp/xmppengine.h new file mode 100644 index 0000000..45d2875 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppengine.h @@ -0,0 +1,338 @@ +/* + * 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: + + //! 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: + //! 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: + + //! 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: + //! 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/third_party/libjingle/files/talk/xmpp/xmppengineimpl.cc b/third_party/libjingle/files/talk/xmpp/xmppengineimpl.cc new file mode 100644 index 0000000..64ff957 --- /dev/null +++ b/third_party/libjingle/files/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()), + output_(new std::stringstream()), + sasl_handler_(NULL) { + 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(jliaw): 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/third_party/libjingle/files/talk/xmpp/xmppengineimpl.h b/third_party/libjingle/files/talk/xmpp/xmppengineimpl.h new file mode 100644 index 0000000..1981035 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppengineimpl.h @@ -0,0 +1,276 @@ +/* + * 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 "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 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_; + scoped_ptr<XmppLoginTask> login_task_; + std::string lang_; + + int next_id_; + Jid bound_jid_; + State state_; + bool encrypted_; + Error error_code_; + int subcode_; + scoped_ptr<XmlElement> stream_error_; + bool raised_reset_; + XmppOutputHandler* output_handler_; + XmppSessionHandler* session_handler_; + + typedef STD_VECTOR(XmppStanzaHandler*) StanzaHandlerVector; + scoped_ptr<StanzaHandlerVector> stanza_handlers_[HL_COUNT]; + + typedef STD_VECTOR(XmppIqEntry*) IqEntryVector; + scoped_ptr<IqEntryVector> iq_entries_; + + scoped_ptr<SaslHandler> sasl_handler_; + + scoped_ptr<std::stringstream> output_; +}; + +} + + +#endif diff --git a/third_party/libjingle/files/talk/xmpp/xmppengineimpl_iq.cc b/third_party/libjingle/files/talk/xmpp/xmppengineimpl_iq.cc new file mode 100644 index 0000000..5834b90 --- /dev/null +++ b/third_party/libjingle/files/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/third_party/libjingle/files/talk/xmpp/xmpplogintask.cc b/third_party/libjingle/files/talk/xmpp/xmpplogintask.cc new file mode 100644 index 0000000..1890c2a --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmpplogintask.cc @@ -0,0 +1,386 @@ +/* + * 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); + } + + auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true"); + auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true"); + + 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) { + if (element->FirstNamed(QN_MISSING_USERNAME) != NULL) { + return Failure(XmppEngine::ERROR_MISSING_USERNAME); + } + 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/third_party/libjingle/files/talk/xmpp/xmpplogintask.h b/third_party/libjingle/files/talk/xmpp/xmpplogintask.h new file mode 100644 index 0000000..993a6bf --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmpplogintask.h @@ -0,0 +1,100 @@ +/* + * 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/base/stl_decl.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_; + scoped_ptr<XmlElement> pelFeatures_; + Jid fullJid_; + std::string streamId_; + scoped_ptr<std::vector<XmlElement *, + std::allocator<XmlElement *> > > pvecQueuedStanzas_; + + scoped_ptr<SaslMechanism> sasl_mech_; + +#ifdef _DEBUG + static const talk_base::ConstantLabel LOGINTASK_STATES[]; +#endif // _DEBUG +}; + +} + +#endif diff --git a/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.cc b/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.cc new file mode 100644 index 0000000..66ed44fb --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.cc @@ -0,0 +1,104 @@ +/* + * 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 <expat.h> +#include "talk/xmllite/xmlelement.h" +#include "talk/base/common.h" +#include "talk/xmpp/xmppstanzaparser.h" +#include "talk/xmpp/constants.h" + +#define new TRACK_NEW + +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/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.h b/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.h new file mode 100644 index 0000000..1e109a3 --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmppstanzaparser.h @@ -0,0 +1,96 @@ +/* + * 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 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/third_party/libjingle/files/talk/xmpp/xmpptask.cc b/third_party/libjingle/files/talk/xmpp/xmpptask.cc new file mode 100644 index 0000000..4756bbe --- /dev/null +++ b/third_party/libjingle/files/talk/xmpp/xmpptask.cc @@ -0,0 +1,174 @@ +/* + * 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(Task* parent, XmppEngine::HandlerLevel level) + : Task(parent), client_(NULL) { +#ifdef _DEBUG + debug_force_timeout_ = false; +#endif + + XmppClient* client = (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); +} + +}
\ No newline at end of file diff --git a/third_party/libjingle/files/talk/xmpp/xmpptask.h b/third_party/libjingle/files/talk/xmpp/xmpptask.h new file mode 100644 index 0000000..156c9a5 --- /dev/null +++ b/third_party/libjingle/files/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::Task* 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); + + bool MatchRequestIq(const XmlElement* stanza, const std::string& type, + const QName& qn); + XmlElement *MakeIqResult(const XmlElement* query); + 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_; + scoped_ptr<XmlElement> next_stanza_; + std::string id_; + +#ifdef _DEBUG + bool debug_force_timeout_; +#endif +}; + +} + +#endif diff --git a/third_party/libjingle/libjingle.gyp b/third_party/libjingle/libjingle.gyp new file mode 100644 index 0000000..80d529f --- /dev/null +++ b/third_party/libjingle/libjingle.gyp @@ -0,0 +1,254 @@ +# Copyright (c) 2009 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +{ + 'includes': [ + '../../build/common.gypi', + ], + 'target_defaults': { + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'FEATURE_ENABLE_VOICEMAIL', # TODO(ncarter): Do we really need this? + 'COMPILER_MSVC', + '_USE_32BIT_TIME_T', + ], + 'include_dirs': [ + './overrides', + './files', + '../third_party/platformsdk_win2008_6_1/files/Include', + ], + 'dependencies': [ + '../expat/expat.gyp:expat', + '../../base/base.gyp:base', + ], + 'direct_dependent_settings': { + 'include_dirs': [ + './overrides', + './files', + ], + 'defines': [ + 'FEATURE_ENABLE_SSL', + 'FEATURE_ENABLE_VOICEMAIL', + ], + }, + }, + 'targets': [ + { + 'target_name': 'libjingle', + 'type': '<(library)', + 'sources': [ + + # 'files/talk/p2p/client/socketmonitor.cc', # unneeded + # 'files/talk/p2p/client/socketmonitor.h', # unneeded + #'files/talk/base/Equifax_Secure_Global_eBusiness_CA-1.h', # openssl + #'files/talk/base/basictypes.h', # overridden + #'files/talk/base/openssladapter.cc', # openssl + #'files/talk/base/openssladapter.h', # openssl + 'files/talk/base/asynchttprequest.cc', + 'files/talk/base/asynchttprequest.h', + 'files/talk/base/asyncpacketsocket.cc', + 'files/talk/base/asyncpacketsocket.h', + 'files/talk/base/asynctcpsocket.h', + 'files/talk/base/asyncudpsocket.cc', + 'files/talk/base/asyncudpsocket.h', + 'files/talk/base/autodetectproxy.cc', + 'files/talk/base/autodetectproxy.h', + 'files/talk/base/base64.cc', + 'files/talk/base/base64.h', + 'files/talk/base/basicdefs.h', + 'files/talk/base/bytebuffer.cc', + 'files/talk/base/bytebuffer.h', + 'files/talk/base/common.cc', + 'files/talk/base/common.h', + 'files/talk/base/convert.h', # win32 only + 'files/talk/base/criticalsection.h', + 'files/talk/base/cryptstring.h', + 'files/talk/base/diskcache.cc', + 'files/talk/base/diskcache.h', + 'files/talk/base/diskcache_win32.cc', # win32 only + 'files/talk/base/diskcache_win32.h', # win32 only + 'files/talk/base/diskcachestd.cc', + 'files/talk/base/diskcachestd.h', + 'files/talk/base/fileutils.cc', + 'files/talk/base/fileutils.h', + 'files/talk/base/firewallsocketserver.cc', + 'files/talk/base/firewallsocketserver.h', + 'files/talk/base/helpers.cc', + 'files/talk/base/helpers.h', + 'files/talk/base/host.cc', + 'files/talk/base/host.h', + 'files/talk/base/httpbase.cc', + 'files/talk/base/httpbase.h', + 'files/talk/base/httpclient.cc', + 'files/talk/base/httpclient.h', + 'files/talk/base/httpcommon-inl.h', + 'files/talk/base/httpcommon.cc', + 'files/talk/base/httpcommon.h', + 'files/talk/base/httpserver.cc', + 'files/talk/base/httpserver.h', + 'files/talk/base/logging.cc', + 'files/talk/base/logging.h', + 'files/talk/base/md5c.c', + 'files/talk/base/md5c.h', + 'files/talk/base/messagequeue.cc', + 'files/talk/base/messagequeue.h', + 'files/talk/base/natserver.cc', + 'files/talk/base/natserver.h', + 'files/talk/base/natserver_main.cc', + 'files/talk/base/natsocketfactory.cc', + 'files/talk/base/natsocketfactory.h', + 'files/talk/base/nattypes.cc', + 'files/talk/base/nattypes.h', + 'files/talk/base/network.cc', + 'files/talk/base/network.h', + 'files/talk/base/pathutils.cc', + 'files/talk/base/pathutils.h', + 'files/talk/base/physicalsocketserver.cc', + 'files/talk/base/physicalsocketserver.h', + 'files/talk/base/proxydetect.cc', + 'files/talk/base/proxydetect.h', + 'files/talk/base/proxyinfo.cc', + 'files/talk/base/proxyinfo.h', + 'files/talk/base/schanneladapter.cc', + 'files/talk/base/schanneladapter.h', + 'files/talk/base/signalthread.cc', + 'files/talk/base/signalthread.h', + 'files/talk/base/socketadapters.cc', + 'files/talk/base/socketadapters.h', + 'files/talk/base/socketaddress.cc', + 'files/talk/base/socketaddress.h', + 'files/talk/base/socketaddresspair.cc', + 'files/talk/base/socketaddresspair.h', + 'files/talk/base/socketfactory.h', + 'files/talk/base/socketpool.cc', + 'files/talk/base/socketpool.h', + 'files/talk/base/socketserver.h', + 'files/talk/base/socketstream.h', + 'files/talk/base/ssladapter.cc', + 'files/talk/base/ssladapter.h', + 'files/talk/base/stl_decl.h', + 'files/talk/base/stream.cc', + 'files/talk/base/stream.h', + 'files/talk/base/streamutils.cc', + 'files/talk/base/streamutils.h', + 'files/talk/base/stringdigest.cc', + 'files/talk/base/stringdigest.h', + 'files/talk/base/stringencode.cc', + 'files/talk/base/stringencode.h', + 'files/talk/base/stringutils.cc', + 'files/talk/base/stringutils.h', + 'files/talk/base/tarstream.cc', + 'files/talk/base/tarstream.h', + 'files/talk/base/task.cc', + 'files/talk/base/task.h', + 'files/talk/base/taskrunner.cc', + 'files/talk/base/taskrunner.h', + 'files/talk/base/testclient.cc', + 'files/talk/base/testclient.h', + 'files/talk/base/thread.cc', + 'files/talk/base/thread.h', + 'files/talk/base/time.cc', + 'files/talk/base/time.h', + 'files/talk/base/urlencode.cc', + 'files/talk/base/urlencode.h', + 'files/talk/base/virtualsocketserver.cc', + 'files/talk/base/virtualsocketserver.h', + 'files/talk/base/win32.h', + 'files/talk/base/win32filesystem.cc', + 'files/talk/base/win32filesystem.h', + 'files/talk/base/win32socketserver.cc', + 'files/talk/base/win32socketserver.h', + 'files/talk/base/win32window.h', + 'files/talk/base/winfirewall.cc', + 'files/talk/base/winfirewall.h', + 'files/talk/base/winping.cc', + 'files/talk/base/winping.h', + 'files/talk/p2p/base/candidate.h', + 'files/talk/p2p/base/common.h', + 'files/talk/p2p/base/constants.cc', + 'files/talk/p2p/base/constants.h', + 'files/talk/p2p/base/p2ptransport.cc', + 'files/talk/p2p/base/p2ptransport.h', + 'files/talk/p2p/base/p2ptransportchannel.cc', + 'files/talk/p2p/base/p2ptransportchannel.h', + 'files/talk/p2p/base/port.cc', + 'files/talk/p2p/base/port.h', + 'files/talk/p2p/base/portallocator.h', + 'files/talk/p2p/base/rawtransport.cc', + 'files/talk/p2p/base/rawtransport.h', + 'files/talk/p2p/base/rawtransportchannel.cc', + 'files/talk/p2p/base/rawtransportchannel.h', + 'files/talk/p2p/base/relayport.cc', + 'files/talk/p2p/base/relayport.h', + 'files/talk/p2p/base/session.cc', + 'files/talk/p2p/base/session.h', + 'files/talk/p2p/base/sessionclient.h', + 'files/talk/p2p/base/sessiondescription.h', + 'files/talk/p2p/base/sessionid.h', + 'files/talk/p2p/base/sessionmanager.cc', + 'files/talk/p2p/base/sessionmanager.h', + 'files/talk/p2p/base/stun.cc', + 'files/talk/p2p/base/stun.h', + 'files/talk/p2p/base/stunport.cc', + 'files/talk/p2p/base/stunport.h', + 'files/talk/p2p/base/stunrequest.cc', + 'files/talk/p2p/base/stunrequest.h', + 'files/talk/p2p/base/tcpport.cc', + 'files/talk/p2p/base/tcpport.h', + 'files/talk/p2p/base/transport.cc', + 'files/talk/p2p/base/transport.h', + 'files/talk/p2p/base/transportchannel.cc', + 'files/talk/p2p/base/transportchannel.h', + 'files/talk/p2p/base/transportchannelimpl.h', + 'files/talk/p2p/base/transportchannelproxy.cc', + 'files/talk/p2p/base/transportchannelproxy.h', + 'files/talk/p2p/base/udpport.cc', + 'files/talk/p2p/base/udpport.h', + 'files/talk/p2p/client/basicportallocator.cc', + 'files/talk/p2p/client/basicportallocator.h', + 'files/talk/p2p/client/httpportallocator.cc', + 'files/talk/p2p/client/httpportallocator.h', + 'files/talk/xmllite/qname.cc', + 'files/talk/xmllite/qname.h', + 'files/talk/xmllite/xmlbuilder.cc', + 'files/talk/xmllite/xmlbuilder.h', + 'files/talk/xmllite/xmlconstants.cc', + 'files/talk/xmllite/xmlconstants.h', + 'files/talk/xmllite/xmlelement.cc', + 'files/talk/xmllite/xmlelement.h', + 'files/talk/xmllite/xmlnsstack.cc', + 'files/talk/xmllite/xmlnsstack.h', + 'files/talk/xmllite/xmlparser.cc', + 'files/talk/xmllite/xmlparser.h', + 'files/talk/xmllite/xmlprinter.cc', + 'files/talk/xmllite/xmlprinter.h', + 'files/talk/xmpp/constants.cc', + 'files/talk/xmpp/constants.h', + 'files/talk/xmpp/jid.cc', + 'files/talk/xmpp/jid.h', + 'files/talk/xmpp/ratelimitmanager.cc', + 'files/talk/xmpp/ratelimitmanager.h', + 'files/talk/xmpp/saslmechanism.cc', + 'files/talk/xmpp/saslmechanism.h', + 'files/talk/xmpp/xmppclient.cc', + 'files/talk/xmpp/xmppclient.h', + 'files/talk/xmpp/xmppengineimpl.cc', + 'files/talk/xmpp/xmppengineimpl.h', + 'files/talk/xmpp/xmppengineimpl_iq.cc', + 'files/talk/xmpp/xmppengineimpl_iq.h', + 'files/talk/xmpp/xmpplogintask.cc', + 'files/talk/xmpp/xmpplogintask.h', + 'files/talk/xmpp/xmppstanzaparser.cc', + 'files/talk/xmpp/xmppstanzaparser.h', + 'files/talk/xmpp/xmpptask.cc', + 'files/talk/xmpp/xmpptask.h', + 'overrides/base/basictypes.h', + 'overrides/base/constructormagic.h', + 'overrides/base/scoped_ptr.h', + 'overrides/config.h', + ], + }, + ], +} + diff --git a/third_party/libjingle/mods-since-v0_4_0.diff b/third_party/libjingle/mods-since-v0_4_0.diff new file mode 100644 index 0000000..2f2cf60 --- /dev/null +++ b/third_party/libjingle/mods-since-v0_4_0.diff @@ -0,0 +1,599 @@ +Only in libjingle/files: .svn +Only in libjingle-0.4.0/: Makefile.in +Only in libjingle-0.4.0/: aclocal.m4 +Only in libjingle-0.4.0/: config.guess +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/config.h libjingle/files/config.h +14c14 +< #define HAVE_ALSA_ASOUNDLIB_H 1 +--- +> /* #undef HAVE_ALSA_ASOUNDLIB_H */ +23c23 +< #define HAVE_GLIB 1 +--- +> /* #undef HAVE_GLIB */ +38c38 +< #define HAVE_ORTP 1 +--- +> /* #undef HAVE_ORTP */ +41c41 +< #define HAVE_SPEEX 1 +--- +> /* #undef HAVE_SPEEX */ +47c47 +< #define HAVE_SPEEX_SPEEX_H 1 +--- +> /* #undef HAVE_SPEEX_SPEEX_H */ +71c71 +< #define LINUX 1 +--- +> /* #undef LINUX */ +113c113 +< #define __ALSA_ENABLED__ 1 +--- +> /* #undef __ALSA_ENABLED__ */ +Only in libjingle-0.4.0/: config.h.in +Only in libjingle-0.4.0/: config.sub +Only in libjingle-0.4.0/: configure +Only in libjingle-0.4.0/: depcomp +Only in libjingle-0.4.0/: install-sh +Only in libjingle-0.4.0/: ltmain.sh +Only in libjingle-0.4.0/: missing +Only in libjingle/files/talk: .svn +Only in libjingle-0.4.0/talk: Makefile.in +Only in libjingle/files/talk/base: .svn +Only in libjingle-0.4.0/talk/base: Makefile.in +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/common.h libjingle/files/talk/base/common.h +54c54 +< #define stdmax(x,y) max(x,y) +--- +> #define stdmax(x,y) _max(x,y) +114,119d113 +< // A macro to disallow the evil copy constructor and operator= functions +< // This should be used in the private: declarations for a class +< #define DISALLOW_EVIL_CONSTRUCTORS(TypeName) \ +< TypeName(const TypeName&); \ +< void operator=(const TypeName&) +< +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/diskcache_win32.cc libjingle/files/talk/base/diskcache_win32.cc +38c38 +< entry->streams = max(entry->streams, index + 1); +--- +> entry->streams = _max(entry->streams, index + 1); +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/helpers.cc libjingle/files/talk/base/helpers.cc +38a39 +> #include <wincrypt.h> +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/httpclient.cc libjingle/files/talk/base/httpclient.cc +670a671 +> HttpAuthContext *context = context_.get(); +676c677,678 +< *context_.use(), response, auth_method); +--- +> context, response, auth_method); +> context_.reset(context); +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/messagequeue.cc libjingle/files/talk/base/messagequeue.cc +98,99c98,99 +< new_ss = true; +< ss_ = new PhysicalSocketServer(); +--- +> default_ss_.reset(new PhysicalSocketServer()); +> ss_ = default_ss_.get(); +108,109d107 +< if (new_ss) +< delete ss_; +113,115d110 +< if (new_ss) +< delete ss_; +< new_ss = false; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/messagequeue.h libjingle/files/talk/base/messagequeue.h +35a36 +> #include "talk/base/scoped_ptr.h" +192a194,195 +> // If a server isn't supplied in the constructor, use this one. +> scoped_ptr<SocketServer> default_ss_; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/proxydetect.cc libjingle/files/talk/base/proxydetect.cc +205,206c205,206 +< const char* list = slist.c_str(); +< while (*list) { +--- +> const char* clist = slist.c_str(); +> while (*clist) { +208,209c208,209 +< if (isspace(*list)) { +< ++list; +--- +> if (isspace(*clist)) { +> ++clist; +214,217c214,217 +< const char * start = list; +< if (const char * end = strchr(list, sep)) { +< len = (end - list); +< list += len + 1; +--- +> const char * start = clist; +> if (const char * end = strchr(clist, sep)) { +> len = (end - clist); +> clist += len + 1; +219,220c219,220 +< len = strlen(list); +< list += len; +--- +> len = strlen(clist); +> clist += len; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/schanneladapter.cc libjingle/files/talk/base/schanneladapter.cc +607c607 +< size_t read = min(cb, readable.size()); +--- +> size_t read = _min(cb, readable.size()); +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/scoped_ptr.h libjingle/files/talk/base/scoped_ptr.h +36,257c36 +< 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 = 0): ptr(p) {} +< +< ~scoped_ptr() { +< typedef char type_must_be_complete[sizeof(T)]; +< delete ptr; +< } +< +< void reset(T* p = 0) { +< typedef char type_must_be_complete[sizeof(T)]; +< +< if (ptr != p) { +< delete 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 & b) { +< T* tmp = b.ptr; +< b.ptr = ptr; +< ptr = tmp; +< } +< +< T* release() { +< T* tmp = ptr; +< ptr = 0; +< return tmp; +< } +< +< T** accept() { +< if (ptr) { +< delete ptr; +< ptr = 0; +< } +< 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 = 0) : ptr(p) {} +< +< ~scoped_array() { +< typedef char type_must_be_complete[sizeof(T)]; +< delete[] ptr; +< } +< +< void reset(T* p = 0) { +< typedef char type_must_be_complete[sizeof(T)]; +< +< if (ptr != p) { +< delete [] ptr; +< ptr = p; +< } +< } +< +< T& operator[](std::ptrdiff_t i) const { +< assert(ptr != 0); +< 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 = 0; +< return tmp; +< } +< +< T** accept() { +< if (ptr) { +< delete [] ptr; +< ptr = 0; +< } +< 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() { +< typedef char type_must_be_complete[sizeof(T)]; +< FF(static_cast<void*>(ptr)); +< } +< +< void reset(T* p = 0) { +< typedef char type_must_be_complete[sizeof(T)]; +< +< 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 +< +< // TODO: get rid of this global using +< using talk_base::scoped_ptr; +--- +> #include "base/scoped_ptr.h" +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/socket.h libjingle/files/talk/base/socket.h +77a78 +> #undef ETIMEDOUT // remove pthread.h's definition +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/stringutils.h libjingle/files/talk/base/stringutils.h +87a88 +> #if 0 +93a95 +> #endif +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/task.cc libjingle/files/talk/base/task.cc +141c141 +< if (aborted_ || done_) +--- +> if (done_) +150c150 +< Wake(); // to self-delete +--- +> GetRunner()->WakeTasks(); +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/base/winping.cc libjingle/files/talk/base/winping.cc +133c133 +< return sizeof(ICMP_ECHO_REPLY) + max(8UL, data_size); +--- +> return sizeof(ICMP_ECHO_REPLY) + _max((uint32)(8UL), data_size); +Only in libjingle-0.4.0/talk: examples +Only in libjingle-0.4.0/talk: libjingle.sln +Only in libjingle-0.4.0/talk: libjingle.vcproj +Only in libjingle/files/talk/p2p: .svn +Only in libjingle-0.4.0/talk/p2p: Makefile.in +Only in libjingle/files/talk/p2p/base: .svn +Only in libjingle-0.4.0/talk/p2p/base: Makefile.in +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/p2p/base/port.cc libjingle/files/talk/p2p/base/port.cc +270c270 +< talk_base::scoped_ptr<StunMessage> stun_msg(new StunMessage()); +--- +> scoped_ptr<StunMessage> stun_msg(new StunMessage()); +Only in libjingle/files/talk/p2p/client: .svn +Only in libjingle-0.4.0/talk/p2p/client: Makefile.in +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/p2p/client/httpportallocator.cc libjingle/files/talk/p2p/client/httpportallocator.cc +82c82 +< relay_hosts_.push_back("relay.l.google.com"); +--- +> relay_hosts_.push_back("relay.google.com"); +Only in libjingle-0.4.0/talk: session +Only in libjingle-0.4.0/talk: third_party +Only in libjingle/files/talk/xmllite: .svn +Only in libjingle-0.4.0/talk/xmllite: Makefile.in +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/qname.cc libjingle/files/talk/xmllite/qname.cc +39c39 +< int result = ns.size() * 101; +--- +> int result = static_cast<int>(ns.size()) * 101; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/xmlelement.cc libjingle/files/talk/xmllite/xmlelement.cc +88c88,89 +< pLastChild_(NULL) { +--- +> pLastChild_(NULL), +> cdata_(false) { +97c98,99 +< pLastChild_(NULL) { +--- +> pLastChild_(NULL), +> cdata_(false) { +125a128 +> cdata_ = elt.cdata_; +133c136,137 +< pLastChild_(NULL) { +--- +> pLastChild_(NULL), +> cdata_(false) { +393a398,403 +> XmlElement::AddCDATAText(const char * buf, int len) { +> cdata_ = true; +> AddParsedText(buf, len); +> } +> +> void +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/xmlelement.h libjingle/files/talk/xmllite/xmlelement.h +203a204,206 +> // Note: CDATA is not supported by XMPP, therefore using this function will +> // generate non-XMPP compatible XML. +> void AddCDATAText(const char * buf, int len); +217a221,222 +> bool IsCDATA() const { return cdata_; } +> +228a234 +> bool cdata_; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/xmlparser.cc libjingle/files/talk/xmllite/xmlparser.cc +28,29d27 +< #include "talk/xmllite/xmlparser.h" +< +35a34 +> #include "talk/xmllite/xmlconstants.h" +38c37 +< #include "talk/xmllite/xmlconstants.h" +--- +> #include "talk/xmllite/xmlparser.h" +119a119,121 +> context_.SetPosition(XML_GetCurrentLineNumber(expat_), +> XML_GetCurrentColumnNumber(expat_), +> XML_GetCurrentByteIndex(expat_)); +127a130,132 +> context_.SetPosition(XML_GetCurrentLineNumber(expat_), +> XML_GetCurrentColumnNumber(expat_), +> XML_GetCurrentByteIndex(expat_)); +134a140,142 +> context_.SetPosition(XML_GetCurrentLineNumber(expat_), +> XML_GetCurrentColumnNumber(expat_), +> XML_GetCurrentByteIndex(expat_)); +168c176,180 +< if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) != XML_STATUS_OK) +--- +> if (XML_Parse(expat_, data, static_cast<int>(len), isFinal) != +> XML_STATUS_OK) { +> context_.SetPosition(XML_GetCurrentLineNumber(expat_), +> XML_GetCurrentColumnNumber(expat_), +> XML_GetCurrentByteIndex(expat_)); +169a182 +> } +193c206,209 +< raised_(XML_ERROR_NONE) { +--- +> raised_(XML_ERROR_NONE), +> line_number_(0), +> column_number_(0), +> byte_index_(0) { +247c263,285 +< XmlParser::ParseContext::~ParseContext() { +--- +> void +> XmlParser::ParseContext::SetPosition(XML_Size line, XML_Size column, +> XML_Index 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_); +> } +249a288 +> XmlParser::ParseContext::~ParseContext() { +251a291 +> } +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/xmlparser.h libjingle/files/talk/xmllite/xmlparser.h +48a49,50 +> virtual void GetPosition(unsigned long * line, unsigned long * column, +> unsigned long * byte_index) = 0; +85a88,89 +> virtual void GetPosition(unsigned long * line, unsigned long * column, +> unsigned long * byte_index); +91a96 +> void SetPosition(XML_Size line, XML_Size column, XML_Index byte_index); +96a102,104 +> XML_Size line_number_; +> XML_Size column_number_; +> XML_Index byte_index_; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmllite/xmlprinter.cc libjingle/files/talk/xmllite/xmlprinter.cc +46a47 +> void PrintCDATAText(const std::string & text); +134c135,138 +< if (pchild->IsText()) +--- +> if (pchild->IsText()) { +> if (element->IsCDATA()) { +> PrintCDATAText(pchild->AsText()->Text()); +> } else { +136c140,141 +< else +--- +> } +> } else +188a194,197 +> void +> XmlPrinterImpl::PrintCDATAText(const std::string & text) { +> *pout_ << "<![CDATA[" << text << "]]>"; +> } +Only in libjingle/files/talk/xmpp: .svn +Only in libjingle-0.4.0/talk/xmpp: Makefile.in +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/constants.cc libjingle/files/talk/xmpp/constants.cc +206a207,209 +> const std::string NS_GOOGLE_AUTH_PROTOCOL("http://www.google.com/talk/protocol/auth"); +> const QName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT(true, NS_GOOGLE_AUTH_PROTOCOL, "client-uses-full-bind-result"); +> +208a212 +> const QName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN(true, NS_GOOGLE_AUTH_PROTOCOL, "allow-non-google-login"); +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/constants.h libjingle/files/talk/xmpp/constants.h +175a176,178 +> extern const std::string NS_GOOGLE_AUTH_PROTOCOL; +> extern const QName QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT; +> extern const QName QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/saslcookiemechanism.h libjingle/files/talk/xmpp/saslcookiemechanism.h +40,41c40,55 +< SaslCookieMechanism(const std::string & mechanism, const std::string & username, const std::string & cookie) : +< mechanism_(mechanism), username_(username), cookie_(cookie) {} +--- +> 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_("") {} +48a63,67 +> if (!token_service_.empty()) { +> el->AddAttr( +> QName(true, "http://www.google.com/talk/protocol/auth", "service"), +> token_service_); +> } +62a82 +> std::string token_service_; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/saslhandler.h libjingle/files/talk/xmpp/saslhandler.h +31a32 +> #include <vector> +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/xmppclientsettings.h libjingle/files/talk/xmpp/xmppclientsettings.h +59a60,62 +> void set_token_service(const std::string & token_service) { +> token_service_ = token_service; +> } +75a79 +> const std::string & token_service() const { return token_service_; } +93a98 +> std::string token_service_; +diff -r --ignore-space-change --strip-trailing-cr libjingle-0.4.0/talk/xmpp/xmpplogintask.cc libjingle/files/talk/xmpp/xmpplogintask.cc +218a219,221 +> auth->SetAttr(QN_GOOGLE_ALLOW_NON_GOOGLE_ID_XMPP_LOGIN, "true"); +> auth->SetAttr(QN_GOOGLE_AUTH_CLIENT_USES_FULL_BIND_RESULT, "true"); +> diff --git a/third_party/libjingle/overrides/config.h b/third_party/libjingle/overrides/config.h new file mode 100644 index 0000000..872d609 --- /dev/null +++ b/third_party/libjingle/overrides/config.h @@ -0,0 +1,121 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file provides a <config.h> include for compiling libjingle. +// This file was generated using libjingle's autoconf utility. +// It fixes compilation in linux. + +/* Chat archiving */ +//#define FEATURE_ENABLE_CHAT_ARCHIVING 1 + +/* Enable SSL */ +//#define FEATURE_ENABLE_SSL 1 + +/* voice mail */ +//#define FEATURE_ENABLE_VOICEMAIL 1 + +/* Define to 1 if you have the <alsa/asoundlib.h> header file. */ +/* #undef HAVE_ALSA_ASOUNDLIB_H */ + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define HAVE_DLFCN_H 1 + +/* Have GIPS Voice Engine */ +#define HAVE_GIPS 1 + +/* Glib is required for oRTP code */ +/* #undef HAVE_GLIB */ + +/* Defined when we have ilbc codec lib */ +/* #undef HAVE_ILBC */ + +/* Define to 1 if you have the <iLBC_decode.h> header file. */ +/* #undef HAVE_ILBC_DECODE_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define HAVE_INTTYPES_H 1 + +/* Define to 1 if you have the <memory.h> header file. */ +#define HAVE_MEMORY_H 1 + +/* Define if you have the <openssl/ssl.h> header file. */ +#define HAVE_OPENSSL_SSL_H 1 + +/* Define if you have semtimedop() for SysV semaphares. */ +#define HAVE_SEMTIMEDOP 1 + +/* has speex */ +/* #undef HAVE_SPEEX */ + +/* Define to 1 if you have the <speex.h> header file. */ +/* #undef HAVE_SPEEX_H */ + +/* Define to 1 if you have the <speex/speex.h> header file. */ +/* #undef HAVE_SPEEX_SPEEX_H */ + +/* Define to 1 if you have the <stdint.h> header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the <strings.h> header file. */ +#define HAVE_STRINGS_H 1 + +/* Define to 1 if you have the <string.h> header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define HAVE_SYS_STAT_H 1 + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the <unistd.h> header file. */ +#define HAVE_UNISTD_H 1 + +/* Building on Linux */ +#define LINUX 1 + +/* Logging */ +#define LOGGING 1 + +/* Building on OSX */ +/* #undef OSX */ + +/* Name of package */ +// #define PACKAGE "dist-zip" + +/* Define to the address where bug reports for this package should be sent. */ +// #define PACKAGE_BUGREPORT "google-talk-open@googlegroups.com" + +/* Define to the full name of this package. */ +// #define PACKAGE_NAME "libjingle" + +/* Define to the full name and version of this package. */ +// #define PACKAGE_STRING "libjingle 0.3.0" + +/* Define to the one symbol short name of this package. */ +// #define PACKAGE_TARNAME "libjingle" + +/* Define to the version of this package. */ +// #define PACKAGE_VERSION "0.3.0" + +/* If we're using configure, we're on POSIX */ +// #define POSIX 1 + +/* Build as a production build */ +//#define PRODUCTION 1 + +/* Build as a production build */ +//#define PRODUCTION_BUILD 1 + +/* Define to 1 if you have the ANSI C header files. */ +#define STDC_HEADERS 1 + +/* Version number of package */ +// #define VERSION "" + +/* Defined when alsa support is enabled */ +/* #undef __ALSA_ENABLED__ */ diff --git a/third_party/libjingle/overrides/talk/base/basictypes.h b/third_party/libjingle/overrides/talk/base/basictypes.h new file mode 100644 index 0000000..ab42029 --- /dev/null +++ b/third_party/libjingle/overrides/talk/base/basictypes.h @@ -0,0 +1,46 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file overrides the inclusion of talk/base/basictypes.h to remove +// collisions with Chromium's base/basictypes.h. We then add back a few +// items that Chromium's version doesn't provide, but libjingle expects. + +#ifndef OVERRIDES_TALK_BASE_BASICTYPES_H__ +#define OVERRIDES_TALK_BASE_BASICTYPES_H__ + +#include "base/basictypes.h" + +#ifndef INT_TYPES_DEFINED +#define INT_TYPES_DEFINED +#ifdef COMPILER_MSVC +typedef __int64 int64; +#else +typedef long long int64; +#endif /* COMPILER_MSVC */ + +#ifdef COMPILER_MSVC +typedef unsigned __int64 uint64; +typedef __int64 int64; +#define INT64_C(x) x ## I64 +#define UINT64_C(x) x ## UI64 +#define INT64_F "I64" +#else +typedef unsigned long long uint64; +typedef long long int64; +#define INT64_C(x) x ## LL +#define UINT64_C(x) x ## ULL +#define INT64_F "ll" +#endif /* COMPILER_MSVC */ +#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; } +} + +#endif // OVERRIDES_TALK_BASE_BASICTYPES_H__ diff --git a/third_party/libjingle/overrides/talk/base/constructormagic.h b/third_party/libjingle/overrides/talk/base/constructormagic.h new file mode 100644 index 0000000..012357e --- /dev/null +++ b/third_party/libjingle/overrides/talk/base/constructormagic.h @@ -0,0 +1,14 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file overrides the inclusion of talk/base/constructormagic.h +// We do this because constructor magic defines DISALLOW_EVIL_CONSTRUCTORS, +// but we want to use the version from Chromium. + +#ifndef OVERRIDES_TALK_BASE_CONSTRUCTORMAGIC_H__ +#define OVERRIDES_TALK_BASE_CONSTRUCTORMAGIC_H__ + +#include "base/basictypes.h" + +#endif // OVERRIDES_TALK_BASE_CONSTRUCTORMAGIC_H__ diff --git a/third_party/libjingle/overrides/talk/base/scoped_ptr.h b/third_party/libjingle/overrides/talk/base/scoped_ptr.h new file mode 100644 index 0000000..bfe5f14 --- /dev/null +++ b/third_party/libjingle/overrides/talk/base/scoped_ptr.h @@ -0,0 +1,20 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file overrides the inclusion of talk/base/scoped_ptr.h. We use +// a version of scoped_ptr from Chromium base, to avoid multiple definitions. + +#ifndef OVERRIDES_TALK_BASE_SCOPED_PTR_H__ +#define OVERRIDES_TALK_BASE_SCOPED_PTR_H__ + +#include "base/scoped_ptr.h" + +namespace talk_base { + +using ::scoped_ptr; +using ::scoped_array; + +} // namespace talk_base + +#endif // OVERRIDES_TALK_BASE_SCOPED_PTR_H__ |